From 16d36d6da92eff18cea390acdcdc910c7fa41a3f Mon Sep 17 00:00:00 2001 From: Nobuhiko Tanibata Date: Thu, 5 Dec 2013 22:12:17 +0900 Subject: Weston 1.3.1 --- .gitignore | 33 + COPYING | 48 + Makefile.am | 9 + README | 15 + autogen.sh | 9 + clients/.gitignore | 47 + clients/Makefile.am | 269 ++ clients/calibrator.c | 277 ++ clients/clickdot.c | 310 ++ clients/cliptest.c | 903 ++++++ clients/desktop-shell.c | 1323 +++++++++ clients/dnd.c | 648 +++++ clients/editor.c | 1250 ++++++++ clients/eventdemo.c | 417 +++ clients/flower.c | 198 ++ clients/fullscreen.c | 368 +++ clients/gears.c | 485 ++++ clients/glmatrix.c | 1062 +++++++ clients/image.c | 426 +++ clients/keyboard.c | 922 ++++++ clients/matrix3.xpm | 692 +++++ clients/multi-resource.c | 600 ++++ clients/nested-client.c | 363 +++ clients/nested.c | 632 +++++ clients/resizor.c | 294 ++ clients/screenshot.c | 309 ++ clients/simple-egl.c | 781 +++++ clients/simple-shm.c | 427 +++ clients/simple-touch.c | 340 +++ clients/smoke.c | 331 +++ clients/subsurfaces.c | 801 ++++++ clients/tablet-shell.c | 478 ++++ clients/terminal.c | 2826 ++++++++++++++++++ clients/transformed.c | 313 ++ clients/view.c | 314 ++ clients/weston-info.c | 457 +++ clients/weston-simple-im.c | 518 ++++ clients/window.c | 5658 +++++++++++++++++++++++++++++++++++++ clients/window.h | 599 ++++ clients/wscreensaver-glue.c | 148 + clients/wscreensaver-glue.h | 120 + clients/wscreensaver.c | 344 +++ clients/wscreensaver.h | 61 + configure.ac | 498 ++++ data/.gitignore | 1 + data/COPYING | 11 + data/Makefile.am | 19 + data/border.png | Bin 0 -> 1969 bytes data/icon_window.png | Bin 0 -> 161 bytes data/pattern.png | Bin 0 -> 1846 bytes data/sign_close.png | Bin 0 -> 235 bytes data/sign_maximize.png | Bin 0 -> 204 bytes data/sign_minimize.png | Bin 0 -> 191 bytes data/terminal.png | Bin 0 -> 1005 bytes data/wayland.svg | 103 + man/.gitignore | 4 + man/Makefile.am | 25 + man/weston-drm.man | 130 + man/weston.ini.man | 378 +++ man/weston.man | 283 ++ notes.txt | 77 + protocol/Makefile.am | 11 + protocol/desktop-shell.xml | 112 + protocol/input-method.xml | 273 ++ protocol/screenshooter.xml | 12 + protocol/subsurface.xml | 244 ++ protocol/tablet-shell.xml | 40 + protocol/text-cursor-position.xml | 11 + protocol/text.xml | 346 +++ protocol/wayland-test.xml | 55 + protocol/workspaces.xml | 27 + protocol/xserver.xml | 18 + shared/Makefile.am | 32 + shared/cairo-util.c | 511 ++++ shared/cairo-util.h | 89 + shared/config-parser.c | 434 +++ shared/config-parser.h | 114 + shared/image-loader.c | 405 +++ shared/image-loader.h | 31 + shared/matrix.c | 273 ++ shared/matrix.h | 82 + shared/option-parser.c | 85 + shared/os-compatibility.c | 180 ++ shared/os-compatibility.h | 54 + shared/zalloc.h | 42 + src/.gitignore | 24 + src/Makefile.am | 326 +++ src/animation.c | 336 +++ src/bindings.c | 342 +++ src/clipboard.c | 300 ++ src/cms-colord.c | 554 ++++ src/cms-helper.c | 132 + src/cms-helper.h | 72 + src/cms-static.c | 113 + src/compositor-drm.c | 2736 ++++++++++++++++++ src/compositor-fbdev.c | 973 +++++++ src/compositor-headless.c | 206 ++ src/compositor-rdp.c | 1094 +++++++ src/compositor-rpi.c | 844 ++++++ src/compositor-wayland.c | 808 ++++++ src/compositor-x11.c | 1621 +++++++++++ src/compositor.c | 3595 +++++++++++++++++++++++ src/compositor.h | 1278 +++++++++ src/data-device.c | 677 +++++ src/evdev-touchpad.c | 800 ++++++ src/evdev.c | 719 +++++ src/evdev.h | 139 + src/filter.c | 338 +++ src/filter.h | 67 + src/gl-renderer.c | 1865 ++++++++++++ src/gl-renderer.h | 106 + src/input.c | 1851 ++++++++++++ src/launcher-util.c | 397 +++ src/launcher-util.h | 48 + src/libbacklight.c | 309 ++ src/libbacklight.h | 49 + src/log.c | 132 + src/noop-renderer.c | 98 + src/pixman-renderer.c | 753 +++++ src/pixman-renderer.h | 37 + src/rpi-bcm-stubs.h | 314 ++ src/rpi-renderer.c | 1594 +++++++++++ src/rpi-renderer.h | 48 + src/screenshooter.c | 591 ++++ src/shell.c | 4799 +++++++++++++++++++++++++++++++ src/spring-tool.c | 66 + src/tablet-shell.c | 575 ++++ src/text-backend.c | 967 +++++++ src/udev-seat.c | 380 +++ src/udev-seat.h | 56 + src/vaapi-recorder.c | 1154 ++++++++ src/vaapi-recorder.h | 35 + src/version.h.in | 37 + src/vertex-clipping.c | 317 +++ src/vertex-clipping.h | 65 + src/weston-egl-ext.h | 80 + src/weston-launch.c | 750 +++++ src/weston-launch.h | 46 + src/weston.pc.in | 11 + src/xwayland/Makefile.am | 40 + src/xwayland/dnd.c | 274 ++ src/xwayland/hash.c | 309 ++ src/xwayland/hash.h | 49 + src/xwayland/launcher.c | 389 +++ src/xwayland/selection.c | 712 +++++ src/xwayland/window-manager.c | 2186 ++++++++++++++ src/xwayland/xwayland.h | 175 ++ src/zoom.c | 397 +++ tests/.gitignore | 13 + tests/Makefile.am | 158 ++ tests/button-test.c | 55 + tests/config-parser-test.c | 203 ++ tests/event-test.c | 418 +++ tests/keyboard-test.c | 65 + tests/matrix-test.c | 419 +++ tests/setbacklight.c | 186 ++ tests/subsurface-test.c | 549 ++++ tests/surface-global-test.c | 80 + tests/surface-test.c | 63 + tests/text-test.c | 214 ++ tests/vertex-clip-test.c | 219 ++ tests/weston-test-client-helper.c | 539 ++++ tests/weston-test-client-helper.h | 123 + tests/weston-test-runner.c | 167 ++ tests/weston-test-runner.h | 76 + tests/weston-test.c | 250 ++ tests/weston-tests-env | 43 + tests/xwayland-test.c | 142 + wayland-scanner.mk | 8 + wcap/.gitignore | 3 + wcap/Makefile.am | 9 + wcap/README | 99 + wcap/main.c | 304 ++ wcap/wcap-decode.c | 152 + wcap/wcap-decode.h | 63 + weston.ini | 66 + 176 files changed, 76276 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 Makefile.am create mode 100644 README create mode 100755 autogen.sh create mode 100644 clients/.gitignore create mode 100644 clients/Makefile.am create mode 100644 clients/calibrator.c create mode 100644 clients/clickdot.c create mode 100644 clients/cliptest.c create mode 100644 clients/desktop-shell.c create mode 100644 clients/dnd.c create mode 100644 clients/editor.c create mode 100644 clients/eventdemo.c create mode 100644 clients/flower.c create mode 100644 clients/fullscreen.c create mode 100644 clients/gears.c create mode 100644 clients/glmatrix.c create mode 100644 clients/image.c create mode 100644 clients/keyboard.c create mode 100644 clients/matrix3.xpm create mode 100644 clients/multi-resource.c create mode 100644 clients/nested-client.c create mode 100644 clients/nested.c create mode 100644 clients/resizor.c create mode 100644 clients/screenshot.c create mode 100644 clients/simple-egl.c create mode 100644 clients/simple-shm.c create mode 100644 clients/simple-touch.c create mode 100644 clients/smoke.c create mode 100644 clients/subsurfaces.c create mode 100644 clients/tablet-shell.c create mode 100644 clients/terminal.c create mode 100644 clients/transformed.c create mode 100644 clients/view.c create mode 100644 clients/weston-info.c create mode 100644 clients/weston-simple-im.c create mode 100644 clients/window.c create mode 100644 clients/window.h create mode 100644 clients/wscreensaver-glue.c create mode 100644 clients/wscreensaver-glue.h create mode 100644 clients/wscreensaver.c create mode 100644 clients/wscreensaver.h create mode 100644 configure.ac create mode 100644 data/.gitignore create mode 100644 data/COPYING create mode 100644 data/Makefile.am create mode 100644 data/border.png create mode 100644 data/icon_window.png create mode 100644 data/pattern.png create mode 100644 data/sign_close.png create mode 100644 data/sign_maximize.png create mode 100644 data/sign_minimize.png create mode 100644 data/terminal.png create mode 100644 data/wayland.svg create mode 100644 man/.gitignore create mode 100644 man/Makefile.am create mode 100644 man/weston-drm.man create mode 100644 man/weston.ini.man create mode 100644 man/weston.man create mode 100644 notes.txt create mode 100644 protocol/Makefile.am create mode 100644 protocol/desktop-shell.xml create mode 100644 protocol/input-method.xml create mode 100644 protocol/screenshooter.xml create mode 100644 protocol/subsurface.xml create mode 100644 protocol/tablet-shell.xml create mode 100644 protocol/text-cursor-position.xml create mode 100644 protocol/text.xml create mode 100644 protocol/wayland-test.xml create mode 100644 protocol/workspaces.xml create mode 100644 protocol/xserver.xml create mode 100644 shared/Makefile.am create mode 100644 shared/cairo-util.c create mode 100644 shared/cairo-util.h create mode 100644 shared/config-parser.c create mode 100644 shared/config-parser.h create mode 100644 shared/image-loader.c create mode 100644 shared/image-loader.h create mode 100644 shared/matrix.c create mode 100644 shared/matrix.h create mode 100644 shared/option-parser.c create mode 100644 shared/os-compatibility.c create mode 100644 shared/os-compatibility.h create mode 100644 shared/zalloc.h create mode 100644 src/.gitignore create mode 100644 src/Makefile.am create mode 100644 src/animation.c create mode 100644 src/bindings.c create mode 100644 src/clipboard.c create mode 100644 src/cms-colord.c create mode 100644 src/cms-helper.c create mode 100644 src/cms-helper.h create mode 100644 src/cms-static.c create mode 100644 src/compositor-drm.c create mode 100644 src/compositor-fbdev.c create mode 100644 src/compositor-headless.c create mode 100644 src/compositor-rdp.c create mode 100644 src/compositor-rpi.c create mode 100644 src/compositor-wayland.c create mode 100644 src/compositor-x11.c create mode 100644 src/compositor.c create mode 100644 src/compositor.h create mode 100644 src/data-device.c create mode 100644 src/evdev-touchpad.c create mode 100644 src/evdev.c create mode 100644 src/evdev.h create mode 100644 src/filter.c create mode 100644 src/filter.h create mode 100644 src/gl-renderer.c create mode 100644 src/gl-renderer.h create mode 100644 src/input.c create mode 100644 src/launcher-util.c create mode 100644 src/launcher-util.h create mode 100644 src/libbacklight.c create mode 100644 src/libbacklight.h create mode 100644 src/log.c create mode 100644 src/noop-renderer.c create mode 100644 src/pixman-renderer.c create mode 100644 src/pixman-renderer.h create mode 100644 src/rpi-bcm-stubs.h create mode 100644 src/rpi-renderer.c create mode 100644 src/rpi-renderer.h create mode 100644 src/screenshooter.c create mode 100644 src/shell.c create mode 100644 src/spring-tool.c create mode 100644 src/tablet-shell.c create mode 100644 src/text-backend.c create mode 100644 src/udev-seat.c create mode 100644 src/udev-seat.h create mode 100644 src/vaapi-recorder.c create mode 100644 src/vaapi-recorder.h create mode 100644 src/version.h.in create mode 100644 src/vertex-clipping.c create mode 100644 src/vertex-clipping.h create mode 100644 src/weston-egl-ext.h create mode 100644 src/weston-launch.c create mode 100644 src/weston-launch.h create mode 100644 src/weston.pc.in create mode 100644 src/xwayland/Makefile.am create mode 100644 src/xwayland/dnd.c create mode 100644 src/xwayland/hash.c create mode 100644 src/xwayland/hash.h create mode 100644 src/xwayland/launcher.c create mode 100644 src/xwayland/selection.c create mode 100644 src/xwayland/window-manager.c create mode 100644 src/xwayland/xwayland.h create mode 100644 src/zoom.c create mode 100644 tests/.gitignore create mode 100644 tests/Makefile.am create mode 100644 tests/button-test.c create mode 100644 tests/config-parser-test.c create mode 100644 tests/event-test.c create mode 100644 tests/keyboard-test.c create mode 100644 tests/matrix-test.c create mode 100644 tests/setbacklight.c create mode 100644 tests/subsurface-test.c create mode 100644 tests/surface-global-test.c create mode 100644 tests/surface-test.c create mode 100644 tests/text-test.c create mode 100644 tests/vertex-clip-test.c create mode 100644 tests/weston-test-client-helper.c create mode 100644 tests/weston-test-client-helper.h create mode 100644 tests/weston-test-runner.c create mode 100644 tests/weston-test-runner.h create mode 100644 tests/weston-test.c create mode 100755 tests/weston-tests-env create mode 100644 tests/xwayland-test.c create mode 100644 wayland-scanner.mk create mode 100644 wcap/.gitignore create mode 100644 wcap/Makefile.am create mode 100644 wcap/README create mode 100644 wcap/main.c create mode 100644 wcap/wcap-decode.c create mode 100644 wcap/wcap-decode.h create mode 100644 weston.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f96bbd19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +*.deps +*.jpg +*.la +*.lo +*.o +*.pc +*.so +*.swp +*~ +ctags +cscope.out +.libs +/aclocal.m4 +/autom4te.cache +/compile +/config.guess +/config.h +/config.h.in +/config.log +/config.mk +/config.status +/config.sub +/configure +/depcomp +/install-sh +/libtool +/ltmain.sh +/missing +/stamp-h1 +/test-driver +Makefile +Makefile.in +TAGS diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..e0b605d0 --- /dev/null +++ b/COPYING @@ -0,0 +1,48 @@ +Copyright © 2008-2012 Kristian Høgsberg +Copyright © 2010-2012 Intel Corporation +Copyright © 2010-2011 Benjamin Franzke +Copyright © 2011-2012 Collabora, Ltd. + +Permission to use, copy, modify, distribute, and sell this software and its +documentation for any purpose is hereby granted without fee, provided that +the above copyright notice appear in all copies and that both that copyright +notice and this permission notice appear in supporting documentation, and +that the name of the copyright holders not be used in advertising or +publicity pertaining to distribution of the software without specific, +written prior permission. The copyright holders make no representations +about the suitability of this software for any purpose. It is provided "as +is" without express or implied warranty. + +THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR +CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +OF THIS SOFTWARE. + + +For libbacklight.c: + +Copyright © 2012 Intel Corporation +Copyright © 2010 Red Hat + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..e9ecc380 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,9 @@ +if BUILD_WCAP_TOOLS +wcap_subdir = wcap +endif + +SUBDIRS = shared src clients data protocol tests $(wcap_subdir) man + +DISTCHECK_CONFIGURE_FLAGS = --disable-setuid-install + +EXTRA_DIST = weston.ini wayland-scanner.mk diff --git a/README b/README new file mode 100644 index 00000000..b3be4c99 --- /dev/null +++ b/README @@ -0,0 +1,15 @@ +Weston + +Weston is the reference implementation of a Wayland compositor, and a +useful compositor in its own right. Weston has various backends that +lets it run on Linux kernel modesetting and evdev input as well as +under X11. Weston ships with a few example clients, from simple +clients that demonstrate certain aspects of the protocol to more +complete clients and a simplistic toolkit. There is also a quite +capable terminal emulator (weston-terminal) and an toy/example desktop +shell. Finally, weston also provides integration with the Xorg server +and can pull X clients into the Wayland desktop and act as a X window +manager. + +Refer to http://wayland.freedesktop.org/building.html for buiding +weston and its dependencies. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..916169a4 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,9 @@ +#! /bin/sh + +test -n "$srcdir" || srcdir=`dirname "$0"` +test -n "$srcdir" || srcdir=. +( + cd "$srcdir" && + autoreconf --force -v --install +) || exit +test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" diff --git a/clients/.gitignore b/clients/.gitignore new file mode 100644 index 00000000..23959cce --- /dev/null +++ b/clients/.gitignore @@ -0,0 +1,47 @@ +weston-calibrator +weston-clickdot +weston-cliptest +weston-dnd +weston-editor +weston-eventdemo +weston-flower +weston-fullscreen +weston-gears +weston-image +weston-nested +weston-nested-client +weston-resizor +weston-simple-egl +weston-simple-shm +weston-simple-touch +weston-smoke +weston-subsurfaces +weston-transformed +weston-view + +desktop-shell-client-protocol.h +desktop-shell-protocol.c +input-method-protocol.c +input-method-client-protocol.h +weston-keyboard +libtoytoolkit.a +screenshooter-client-protocol.h +screenshooter-protocol.c +subsurface-client-protocol.h +subsurface-protocol.c +tablet-shell-client-protocol.h +tablet-shell-protocol.c +text-client-protocol.h +text-cursor-position-client-protocol.h +text-cursor-position-protocol.c +text-protocol.c +weston-desktop-shell +weston-info +weston-screensaver +weston-screenshooter +weston-tablet-shell +weston-terminal +weston-multi-resource +workspaces-client-protocol.h +workspaces-protocol.c +weston-simple-im diff --git a/clients/Makefile.am b/clients/Makefile.am new file mode 100644 index 00000000..4f9dc481 --- /dev/null +++ b/clients/Makefile.am @@ -0,0 +1,269 @@ +bin_PROGRAMS = \ + weston-info \ + $(terminal) + +demo_clients = \ + $(clients_programs) \ + $(pango_programs) \ + $(poppler_programs) \ + $(simple_clients_programs) \ + $(simple_egl_clients_programs) + +if ENABLE_DEMO_CLIENTS +bin_PROGRAMS += $(demo_clients) +else +noinst_PROGRAMS = $(demo_clients) +endif + +libexec_PROGRAMS = \ + $(desktop_shell) \ + $(tablet_shell) \ + $(screenshooter) \ + $(screensaver) \ + $(keyboard) \ + weston-simple-im + +AM_CFLAGS = $(GCC_CFLAGS) +AM_CPPFLAGS = \ + -DDATADIR='"$(datadir)"' \ + -DBINDIR='"$(bindir)"' \ + $(CLIENT_CFLAGS) $(CAIRO_EGL_CFLAGS) + +if BUILD_SIMPLE_CLIENTS +simple_clients_programs = \ + weston-simple-shm \ + weston-simple-touch \ + weston-multi-resource + +weston_simple_shm_SOURCES = simple-shm.c \ + ../shared/os-compatibility.c \ + ../shared/os-compatibility.h +weston_simple_shm_CPPFLAGS = $(SIMPLE_CLIENT_CFLAGS) +weston_simple_shm_LDADD = $(SIMPLE_CLIENT_LIBS) + +weston_simple_touch_SOURCES = simple-touch.c \ + ../shared/os-compatibility.c \ + ../shared/os-compatibility.h +weston_simple_touch_CPPFLAGS = $(SIMPLE_CLIENT_CFLAGS) +weston_simple_touch_LDADD = $(SIMPLE_CLIENT_LIBS) + +weston_multi_resource_SOURCES = multi-resource.c \ + ../shared/os-compatibility.c \ + ../shared/os-compatibility.h +weston_multi_resource_CPPFLAGS = $(SIMPLE_CLIENT_CFLAGS) +weston_multi_resource_LDADD = $(SIMPLE_CLIENT_LIBS) -lm +endif + +if BUILD_SIMPLE_EGL_CLIENTS +simple_egl_clients_programs = \ + weston-simple-egl + +weston_simple_egl_SOURCES = simple-egl.c +weston_simple_egl_CPPFLAGS = $(SIMPLE_EGL_CLIENT_CFLAGS) +weston_simple_egl_LDADD = $(SIMPLE_EGL_CLIENT_LIBS) -lm +endif + +if BUILD_CLIENTS +terminal = weston-terminal + +clients_programs = \ + weston-flower \ + weston-image \ + weston-cliptest \ + weston-dnd \ + weston-smoke \ + weston-resizor \ + weston-eventdemo \ + weston-clickdot \ + weston-transformed \ + weston-fullscreen \ + weston-calibrator \ + $(subsurfaces) \ + $(full_gl_client_programs) \ + $(cairo_glesv2_programs) + +desktop_shell = weston-desktop-shell + +if ENABLE_TABLET_SHELL +tablet_shell = weston-tablet-shell +endif + +screenshooter = weston-screenshooter + +noinst_LTLIBRARIES = libtoytoolkit.la + +libtoytoolkit_la_SOURCES = \ + window.c \ + window.h \ + text-cursor-position-protocol.c \ + text-cursor-position-client-protocol.h \ + subsurface-protocol.c \ + subsurface-client-protocol.h \ + workspaces-protocol.c \ + workspaces-client-protocol.h + +libtoytoolkit_la_LIBADD = \ + $(CLIENT_LIBS) \ + $(CAIRO_EGL_LIBS) \ + ../shared/libshared-cairo.la -lrt -lm + +weston_flower_SOURCES = flower.c +weston_flower_LDADD = libtoytoolkit.la + +weston_screenshooter_SOURCES = \ + screenshot.c \ + screenshooter-protocol.c \ + screenshooter-client-protocol.h \ + ../shared/os-compatibility.c \ + ../shared/os-compatibility.h +weston_screenshooter_LDADD = $(CLIENT_LIBS) + +weston_terminal_SOURCES = terminal.c +weston_terminal_LDADD = libtoytoolkit.la -lutil + +weston_image_SOURCES = image.c +weston_image_LDADD = libtoytoolkit.la + +weston_cliptest_SOURCES = cliptest.c +weston_cliptest_CPPFLAGS = $(AM_CPPFLAGS) $(PIXMAN_CFLAGS) +weston_cliptest_LDADD = libtoytoolkit.la $(PIXMAN_LIBS) + +weston_dnd_SOURCES = dnd.c +weston_dnd_LDADD = libtoytoolkit.la + +weston_smoke_SOURCES = smoke.c +weston_smoke_LDADD = libtoytoolkit.la + +weston_resizor_SOURCES = resizor.c +weston_resizor_LDADD = libtoytoolkit.la + +if HAVE_CAIRO_GLESV2 +cairo_glesv2_programs = weston-nested weston-nested-client + +weston_nested_SOURCES = nested.c +weston_nested_LDADD = libtoytoolkit.la $(SERVER_LIBS) + +weston_nested_client_SOURCES = nested-client.c +weston_nested_client_LDADD = $(SIMPLE_EGL_CLIENT_LIBS) -lm +endif + +weston_eventdemo_SOURCES = eventdemo.c +weston_eventdemo_LDADD = libtoytoolkit.la + +weston_clickdot_SOURCES = clickdot.c +weston_clickdot_LDADD = libtoytoolkit.la + +weston_transformed_SOURCES = transformed.c +weston_transformed_LDADD = libtoytoolkit.la + +weston_fullscreen_SOURCES = fullscreen.c +weston_fullscreen_LDADD = libtoytoolkit.la + +weston_calibrator_SOURCES = calibrator.c \ + ../shared/matrix.c \ + ../shared/matrix.h +weston_calibrator_LDADD = libtoytoolkit.la + +if BUILD_SUBSURFACES_CLIENT +subsurfaces = weston-subsurfaces +weston_subsurfaces_SOURCES = subsurfaces.c +weston_subsurfaces_CPPFLAGS = $(AM_CPPFLAGS) $(SIMPLE_EGL_CLIENT_CFLAGS) +weston_subsurfaces_LDADD = libtoytoolkit.la $(SIMPLE_EGL_CLIENT_LIBS) -lm +endif + +if HAVE_PANGO +pango_programs = weston-editor +weston_editor_SOURCES = \ + editor.c \ + text-protocol.c \ + text-client-protocol.h +weston_editor_LDADD = libtoytoolkit.la $(PANGO_LIBS) +weston_editor_CPPFLAGS = $(AM_CPPFLAGS) $(PANGO_CFLAGS) +endif + +keyboard = weston-keyboard +weston_keyboard_SOURCES = \ + keyboard.c \ + desktop-shell-client-protocol.h \ + desktop-shell-protocol.c \ + input-method-protocol.c \ + input-method-client-protocol.h +weston_keyboard_LDADD = libtoytoolkit.la + +weston_simple_im_SOURCES = \ + weston-simple-im.c \ + input-method-protocol.c \ + input-method-client-protocol.h +weston_simple_im_LDADD = $(CLIENT_LIBS) + +weston_info_SOURCES = \ + weston-info.c \ + ../shared/os-compatibility.c \ + ../shared/os-compatibility.h +weston_info_LDADD = $(WESTON_INFO_LIBS) + +weston_desktop_shell_SOURCES = \ + desktop-shell.c \ + desktop-shell-client-protocol.h \ + desktop-shell-protocol.c +weston_desktop_shell_LDADD = libtoytoolkit.la + +weston_tablet_shell_SOURCES = \ + tablet-shell.c \ + tablet-shell-client-protocol.h \ + tablet-shell-protocol.c +weston_tablet_shell_LDADD = libtoytoolkit.la + +BUILT_SOURCES = \ + screenshooter-client-protocol.h \ + screenshooter-protocol.c \ + text-cursor-position-client-protocol.h \ + text-cursor-position-protocol.c \ + text-protocol.c \ + text-client-protocol.h \ + input-method-protocol.c \ + input-method-client-protocol.h \ + desktop-shell-client-protocol.h \ + desktop-shell-protocol.c \ + tablet-shell-client-protocol.h \ + tablet-shell-protocol.c \ + subsurface-client-protocol.h \ + subsurface-protocol.c \ + workspaces-client-protocol.h \ + workspaces-protocol.c + +CLEANFILES = $(BUILT_SOURCES) +endif + +if BUILD_FULL_GL_CLIENTS +full_gl_client_programs = weston-gears + +weston_gears_SOURCES = gears.c +weston_gears_LDADD = libtoytoolkit.la + +if HAVE_GLU +screensaver = weston-screensaver +weston_screensaver_SOURCES = \ + wscreensaver.c \ + wscreensaver.h \ + desktop-shell-client-protocol.h \ + desktop-shell-protocol.c \ + wscreensaver-glue.c \ + wscreensaver-glue.h \ + glmatrix.c \ + matrix3.xpm +weston_screensaver_LDADD = libtoytoolkit.la $(GLU_LIBS) +weston_screensaver_CFLAGS = $(GLU_CFLAGS) +endif + +endif + +wayland_protocoldir = $(top_srcdir)/protocol +include $(top_srcdir)/wayland-scanner.mk + +if HAVE_POPPLER +poppler_programs = weston-view +weston_view_SOURCES = view.c +weston_view_LDADD = libtoytoolkit.la $(POPPLER_LIBS) +weston_view_CPPFLAGS = $(AM_CPPFLAGS) $(POPPLER_CFLAGS) +endif diff --git a/clients/calibrator.c b/clients/calibrator.c new file mode 100644 index 00000000..783cdecc --- /dev/null +++ b/clients/calibrator.c @@ -0,0 +1,277 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" +#include "../shared/matrix.h" + +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) + +/* Our points for the calibration must be not be on a line */ +static const struct { + float x_ratio, y_ratio; +} test_ratios[] = { + { 0.20, 0.40 }, + { 0.80, 0.60 }, + { 0.40, 0.80 } +}; + +struct calibrator { + struct tests { + int32_t drawn_x, drawn_y; + int32_t clicked_x, clicked_y; + } tests[ARRAY_LENGTH(test_ratios)]; + int current_test; + + struct display *display; + struct window *window; + struct widget *widget; +}; + +/* + * Calibration algorithm: + * + * The equation we want to apply at event time where x' and y' are the + * calibrated co-ordinates. + * + * x' = Ax + By + C + * y' = Dx + Ey + F + * + * For example "zero calibration" would be A=1.0 B=0.0 C=0.0, D=0.0, E=1.0, + * and F=0.0. + * + * With 6 unknowns we need 6 equations to find the constants: + * + * x1' = Ax1 + By1 + C + * y1' = Dx1 + Ey1 + F + * ... + * x3' = Ax3 + By3 + C + * y3' = Dx3 + Ey3 + F + * + * In matrix form: + * + * x1' x1 y1 1 A + * x2' = x2 y2 1 x B + * x3' x3 y3 1 C + * + * So making the matrix M we can find the constants with: + * + * A x1' + * B = M^-1 x x2' + * C x3' + * + * (and similarly for D, E and F) + * + * For the calibration the desired values x, y are the same values at which + * we've drawn at. + * + */ +static void +finish_calibration (struct calibrator *calibrator) +{ + struct weston_matrix m; + struct weston_matrix inverse; + struct weston_vector x_calib, y_calib; + int i; + + + /* + * x1 y1 1 0 + * x2 y2 1 0 + * x3 y3 1 0 + * 0 0 0 1 + */ + memset(&m, 0, sizeof(m)); + for (i = 0; i < (int)ARRAY_LENGTH(test_ratios); i++) { + m.d[i] = calibrator->tests[i].clicked_x; + m.d[i + 4] = calibrator->tests[i].clicked_y; + m.d[i + 8] = 1; + } + m.d[15] = 1; + + weston_matrix_invert(&inverse, &m); + + memset(&x_calib, 0, sizeof(x_calib)); + memset(&y_calib, 0, sizeof(y_calib)); + + for (i = 0; i < (int)ARRAY_LENGTH(test_ratios); i++) { + x_calib.f[i] = calibrator->tests[i].drawn_x; + y_calib.f[i] = calibrator->tests[i].drawn_y; + } + + /* Multiples into the vector */ + weston_matrix_transform(&inverse, &x_calib); + weston_matrix_transform(&inverse, &y_calib); + + printf ("Calibration values: %f %f %f %f %f %f\n", + x_calib.f[0], x_calib.f[1], x_calib.f[2], + y_calib.f[0], y_calib.f[1], y_calib.f[2]); + + exit(0); +} + + +static void +button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct calibrator *calibrator = data; + int32_t x, y; + + if (state == WL_POINTER_BUTTON_STATE_PRESSED && button == BTN_LEFT) { + input_get_position(input, &x, &y); + calibrator->tests[calibrator->current_test].clicked_x = x; + calibrator->tests[calibrator->current_test].clicked_y = y; + + calibrator->current_test--; + if (calibrator->current_test < 0) + finish_calibration(calibrator); + } + + widget_schedule_redraw(widget); +} + +static void +touch_handler(struct widget *widget, struct input *input, uint32_t serial, + uint32_t time, int32_t id, float x, float y, void *data) +{ + struct calibrator *calibrator = data; + + calibrator->tests[calibrator->current_test].clicked_x = x; + calibrator->tests[calibrator->current_test].clicked_y = y; + calibrator->current_test--; + + if (calibrator->current_test < 0) + finish_calibration(calibrator); + + widget_schedule_redraw(widget); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct calibrator *calibrator = data; + struct rectangle allocation; + cairo_surface_t *surface; + cairo_t *cr; + int32_t drawn_x, drawn_y; + + widget_get_allocation(calibrator->widget, &allocation); + surface = window_get_surface(calibrator->window); + + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); + cairo_paint(cr); + + drawn_x = test_ratios[calibrator->current_test].x_ratio * allocation.width; + drawn_y = test_ratios[calibrator->current_test].y_ratio * allocation.height; + + calibrator->tests[calibrator->current_test].drawn_x = drawn_x; + calibrator->tests[calibrator->current_test].drawn_y = drawn_y; + + cairo_translate(cr, drawn_x, drawn_y); + cairo_set_line_width(cr, 2.0); + cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); + cairo_move_to(cr, 0, -10.0); + cairo_line_to(cr, 0, 10.0); + cairo_stroke(cr); + cairo_move_to(cr, -10.0, 0); + cairo_line_to(cr, 10.0, 0.0); + cairo_stroke(cr); + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static struct calibrator * +calibrator_create(struct display *display) +{ + struct calibrator *calibrator; + + calibrator = malloc(sizeof *calibrator); + if (calibrator == NULL) + return NULL; + + calibrator->window = window_create(display); + calibrator->widget = window_add_widget(calibrator->window, calibrator); + window_set_title(calibrator->window, "Wayland calibrator"); + calibrator->display = display; + + calibrator->current_test = ARRAY_LENGTH(test_ratios) - 1; + + widget_set_button_handler(calibrator->widget, button_handler); + widget_set_touch_down_handler(calibrator->widget, touch_handler); + widget_set_redraw_handler(calibrator->widget, redraw_handler); + + window_set_fullscreen(calibrator->window, 1); + + return calibrator; +} + +static void +calibrator_destroy(struct calibrator *calibrator) +{ + widget_destroy(calibrator->widget); + window_destroy(calibrator->window); + free(calibrator); +} + + +int +main(int argc, char *argv[]) +{ + struct display *display; + struct calibrator *calibrator; + + display = display_create(&argc, argv); + + if (display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + calibrator = calibrator_create(display); + + if (!calibrator) + return -1; + + display_run(display); + + calibrator_destroy(calibrator); + display_destroy(display); + + return 0; +} + diff --git a/clients/clickdot.c b/clients/clickdot.c new file mode 100644 index 00000000..aebe4db1 --- /dev/null +++ b/clients/clickdot.c @@ -0,0 +1,310 @@ +/* + * Copyright © 2010 Intel Corporation + * Copyright © 2012 Collabora, Ltd. + * Copyright © 2012 Jonas Ådahl + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" + +struct clickdot { + struct display *display; + struct window *window; + struct widget *widget; + + cairo_surface_t *buffer; + + struct { + int32_t x, y; + } dot; + + struct { + int32_t x, y; + int32_t old_x, old_y; + } line; + + int reset; +}; + +static void +draw_line(struct clickdot *clickdot, cairo_t *cr, + struct rectangle *allocation) +{ + cairo_t *bcr; + cairo_surface_t *tmp_buffer = NULL; + + if (clickdot->reset) { + tmp_buffer = clickdot->buffer; + clickdot->buffer = NULL; + clickdot->line.x = -1; + clickdot->line.y = -1; + clickdot->line.old_x = -1; + clickdot->line.old_y = -1; + clickdot->reset = 0; + } + + if (clickdot->buffer == NULL) { + clickdot->buffer = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + allocation->width, + allocation->height); + bcr = cairo_create(clickdot->buffer); + cairo_set_source_rgba(bcr, 0, 0, 0, 0); + cairo_rectangle(bcr, + 0, 0, + allocation->width, allocation->height); + cairo_fill(bcr); + } + else + bcr = cairo_create(clickdot->buffer); + + if (tmp_buffer) { + cairo_set_source_surface(bcr, tmp_buffer, 0, 0); + cairo_rectangle(bcr, 0, 0, + allocation->width, allocation->height); + cairo_clip(bcr); + cairo_paint(bcr); + + cairo_surface_destroy(tmp_buffer); + } + + if (clickdot->line.x != -1 && clickdot->line.y != -1) { + if (clickdot->line.old_x != -1 && + clickdot->line.old_y != -1) { + cairo_set_line_width(bcr, 2.0); + cairo_set_source_rgb(bcr, 1, 1, 1); + cairo_translate(bcr, + -allocation->x, -allocation->y); + + cairo_move_to(bcr, + clickdot->line.old_x, + clickdot->line.old_y); + cairo_line_to(bcr, + clickdot->line.x, + clickdot->line.y); + + cairo_stroke(bcr); + } + + clickdot->line.old_x = clickdot->line.x; + clickdot->line.old_y = clickdot->line.y; + } + cairo_destroy(bcr); + + cairo_set_source_surface(cr, clickdot->buffer, + allocation->x, allocation->y); + cairo_set_operator(cr, CAIRO_OPERATOR_ADD); + cairo_rectangle(cr, + allocation->x, allocation->y, + allocation->width, allocation->height); + cairo_clip(cr); + cairo_paint(cr); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + static const double r = 10.0; + struct clickdot *clickdot = data; + cairo_surface_t *surface; + cairo_t *cr; + struct rectangle allocation; + + widget_get_allocation(clickdot->widget, &allocation); + + surface = window_get_surface(clickdot->window); + + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_rectangle(cr, + allocation.x, + allocation.y, + allocation.width, + allocation.height); + cairo_set_source_rgba(cr, 0, 0, 0, 0.8); + cairo_fill(cr); + + draw_line(clickdot, cr, &allocation); + + cairo_translate(cr, clickdot->dot.x + 0.5, clickdot->dot.y + 0.5); + cairo_set_line_width(cr, 1.0); + cairo_set_source_rgb(cr, 0.1, 0.9, 0.9); + cairo_move_to(cr, 0.0, -r); + cairo_line_to(cr, 0.0, r); + cairo_move_to(cr, -r, 0.0); + cairo_line_to(cr, r, 0.0); + cairo_arc(cr, 0.0, 0.0, r, 0.0, 2.0 * M_PI); + cairo_stroke(cr); + + cairo_destroy(cr); + + cairo_surface_destroy(surface); +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct clickdot *clickdot = data; + + window_schedule_redraw(clickdot->window); +} + +static void +key_handler(struct window *window, struct input *input, uint32_t time, + uint32_t key, uint32_t sym, + enum wl_keyboard_key_state state, void *data) +{ + struct clickdot *clickdot = data; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + switch (sym) { + case XKB_KEY_Escape: + display_exit(clickdot->display); + break; + } +} + +static void +button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct clickdot *clickdot = data; + + if (state == WL_POINTER_BUTTON_STATE_PRESSED && button == BTN_LEFT) + input_get_position(input, &clickdot->dot.x, &clickdot->dot.y); + + widget_schedule_redraw(widget); +} + +static int +motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct clickdot *clickdot = data; + clickdot->line.x = x; + clickdot->line.y = y; + + window_schedule_redraw(clickdot->window); + + return CURSOR_LEFT_PTR; +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, + void *data) +{ + struct clickdot *clickdot = data; + + clickdot->reset = 1; +} + +static void +leave_handler(struct widget *widget, + struct input *input, void *data) +{ + struct clickdot *clickdot = data; + + clickdot->reset = 1; +} + +static struct clickdot * +clickdot_create(struct display *display) +{ + struct clickdot *clickdot; + + clickdot = xzalloc(sizeof *clickdot); + clickdot->window = window_create(display); + clickdot->widget = frame_create(clickdot->window, clickdot); + window_set_title(clickdot->window, "Wayland ClickDot"); + clickdot->display = display; + clickdot->buffer = NULL; + + window_set_key_handler(clickdot->window, key_handler); + window_set_user_data(clickdot->window, clickdot); + window_set_keyboard_focus_handler(clickdot->window, + keyboard_focus_handler); + + widget_set_redraw_handler(clickdot->widget, redraw_handler); + widget_set_button_handler(clickdot->widget, button_handler); + widget_set_motion_handler(clickdot->widget, motion_handler); + widget_set_resize_handler(clickdot->widget, resize_handler); + widget_set_leave_handler(clickdot->widget, leave_handler); + + widget_schedule_resize(clickdot->widget, 500, 400); + clickdot->dot.x = 250; + clickdot->dot.y = 200; + clickdot->line.x = -1; + clickdot->line.y = -1; + clickdot->line.old_x = -1; + clickdot->line.old_y = -1; + clickdot->reset = 0; + + return clickdot; +} + +static void +clickdot_destroy(struct clickdot *clickdot) +{ + if (clickdot->buffer) + cairo_surface_destroy(clickdot->buffer); + widget_destroy(clickdot->widget); + window_destroy(clickdot->window); + free(clickdot); +} + +int +main(int argc, char *argv[]) +{ + struct display *display; + struct clickdot *clickdot; + + display = display_create(&argc, argv); + if (display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + clickdot = clickdot_create(display); + + display_run(display); + + clickdot_destroy(clickdot); + display_destroy(display); + + return 0; +} diff --git a/clients/cliptest.c b/clients/cliptest.c new file mode 100644 index 00000000..4b778087 --- /dev/null +++ b/clients/cliptest.c @@ -0,0 +1,903 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * Copyright © 2012 Rob Clark + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +/* cliptest: for debugging calculate_edges() function, which is copied + * from compositor.c. + * controls: + * clip box position: mouse left drag, keys: w a s d + * clip box size: mouse right drag, keys: i j k l + * surface orientation: mouse wheel, keys: n m + * surface transform disable key: r + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" + +typedef float GLfloat; + +struct geometry { + pixman_box32_t clip; + + pixman_box32_t surf; + float s; /* sin phi */ + float c; /* cos phi */ + float phi; +}; + +struct weston_surface { + struct { + int enabled; + } transform; + + struct geometry *geometry; +}; + +static void +weston_surface_to_global_float(struct weston_surface *surface, + GLfloat sx, GLfloat sy, GLfloat *x, GLfloat *y) +{ + struct geometry *g = surface->geometry; + + /* pure rotation around origin by sine and cosine */ + *x = g->c * sx + g->s * sy; + *y = -g->s * sx + g->c * sy; +} + +/* ---------------------- copied begins -----------------------*/ + +struct polygon8 { + GLfloat x[8]; + GLfloat y[8]; + int n; +}; + +struct clip_context { + struct { + GLfloat x; + GLfloat y; + } prev; + + struct { + GLfloat x1, y1; + GLfloat x2, y2; + } clip; + + struct { + GLfloat *x; + GLfloat *y; + } vertices; +}; + +static GLfloat +float_difference(GLfloat a, GLfloat b) +{ + /* http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/ */ + static const GLfloat max_diff = 4.0f * FLT_MIN; + static const GLfloat max_rel_diff = 4.0e-5; + GLfloat diff = a - b; + GLfloat adiff = fabsf(diff); + + if (adiff <= max_diff) + return 0.0f; + + a = fabsf(a); + b = fabsf(b); + if (adiff <= (a > b ? a : b) * max_rel_diff) + return 0.0f; + + return diff; +} + +/* A line segment (p1x, p1y)-(p2x, p2y) intersects the line x = x_arg. + * Compute the y coordinate of the intersection. + */ +static GLfloat +clip_intersect_y(GLfloat p1x, GLfloat p1y, GLfloat p2x, GLfloat p2y, + GLfloat x_arg) +{ + GLfloat a; + GLfloat diff = float_difference(p1x, p2x); + + /* Practically vertical line segment, yet the end points have already + * been determined to be on different sides of the line. Therefore + * the line segment is part of the line and intersects everywhere. + * Return the end point, so we use the whole line segment. + */ + if (diff == 0.0f) + return p2y; + + a = (x_arg - p2x) / diff; + return p2y + (p1y - p2y) * a; +} + +/* A line segment (p1x, p1y)-(p2x, p2y) intersects the line y = y_arg. + * Compute the x coordinate of the intersection. + */ +static GLfloat +clip_intersect_x(GLfloat p1x, GLfloat p1y, GLfloat p2x, GLfloat p2y, + GLfloat y_arg) +{ + GLfloat a; + GLfloat diff = float_difference(p1y, p2y); + + /* Practically horizontal line segment, yet the end points have already + * been determined to be on different sides of the line. Therefore + * the line segment is part of the line and intersects everywhere. + * Return the end point, so we use the whole line segment. + */ + if (diff == 0.0f) + return p2x; + + a = (y_arg - p2y) / diff; + return p2x + (p1x - p2x) * a; +} + +enum path_transition { + PATH_TRANSITION_OUT_TO_OUT = 0, + PATH_TRANSITION_OUT_TO_IN = 1, + PATH_TRANSITION_IN_TO_OUT = 2, + PATH_TRANSITION_IN_TO_IN = 3, +}; + +static void +clip_append_vertex(struct clip_context *ctx, GLfloat x, GLfloat y) +{ + *ctx->vertices.x++ = x; + *ctx->vertices.y++ = y; +} + +static enum path_transition +path_transition_left_edge(struct clip_context *ctx, GLfloat x, GLfloat y) +{ + return ((ctx->prev.x >= ctx->clip.x1) << 1) | (x >= ctx->clip.x1); +} + +static enum path_transition +path_transition_right_edge(struct clip_context *ctx, GLfloat x, GLfloat y) +{ + return ((ctx->prev.x < ctx->clip.x2) << 1) | (x < ctx->clip.x2); +} + +static enum path_transition +path_transition_top_edge(struct clip_context *ctx, GLfloat x, GLfloat y) +{ + return ((ctx->prev.y >= ctx->clip.y1) << 1) | (y >= ctx->clip.y1); +} + +static enum path_transition +path_transition_bottom_edge(struct clip_context *ctx, GLfloat x, GLfloat y) +{ + return ((ctx->prev.y < ctx->clip.y2) << 1) | (y < ctx->clip.y2); +} + +static void +clip_polygon_leftright(struct clip_context *ctx, + enum path_transition transition, + GLfloat x, GLfloat y, GLfloat clip_x) +{ + GLfloat yi; + + switch (transition) { + case PATH_TRANSITION_IN_TO_IN: + clip_append_vertex(ctx, x, y); + break; + case PATH_TRANSITION_IN_TO_OUT: + yi = clip_intersect_y(ctx->prev.x, ctx->prev.y, x, y, clip_x); + clip_append_vertex(ctx, clip_x, yi); + break; + case PATH_TRANSITION_OUT_TO_IN: + yi = clip_intersect_y(ctx->prev.x, ctx->prev.y, x, y, clip_x); + clip_append_vertex(ctx, clip_x, yi); + clip_append_vertex(ctx, x, y); + break; + case PATH_TRANSITION_OUT_TO_OUT: + /* nothing */ + break; + default: + assert(0 && "bad enum path_transition"); + } + + ctx->prev.x = x; + ctx->prev.y = y; +} + +static void +clip_polygon_topbottom(struct clip_context *ctx, + enum path_transition transition, + GLfloat x, GLfloat y, GLfloat clip_y) +{ + GLfloat xi; + + switch (transition) { + case PATH_TRANSITION_IN_TO_IN: + clip_append_vertex(ctx, x, y); + break; + case PATH_TRANSITION_IN_TO_OUT: + xi = clip_intersect_x(ctx->prev.x, ctx->prev.y, x, y, clip_y); + clip_append_vertex(ctx, xi, clip_y); + break; + case PATH_TRANSITION_OUT_TO_IN: + xi = clip_intersect_x(ctx->prev.x, ctx->prev.y, x, y, clip_y); + clip_append_vertex(ctx, xi, clip_y); + clip_append_vertex(ctx, x, y); + break; + case PATH_TRANSITION_OUT_TO_OUT: + /* nothing */ + break; + default: + assert(0 && "bad enum path_transition"); + } + + ctx->prev.x = x; + ctx->prev.y = y; +} + +static void +clip_context_prepare(struct clip_context *ctx, const struct polygon8 *src, + GLfloat *dst_x, GLfloat *dst_y) +{ + ctx->prev.x = src->x[src->n - 1]; + ctx->prev.y = src->y[src->n - 1]; + ctx->vertices.x = dst_x; + ctx->vertices.y = dst_y; +} + +static int +clip_polygon_left(struct clip_context *ctx, const struct polygon8 *src, + GLfloat *dst_x, GLfloat *dst_y) +{ + enum path_transition trans; + int i; + + clip_context_prepare(ctx, src, dst_x, dst_y); + for (i = 0; i < src->n; i++) { + trans = path_transition_left_edge(ctx, src->x[i], src->y[i]); + clip_polygon_leftright(ctx, trans, src->x[i], src->y[i], + ctx->clip.x1); + } + return ctx->vertices.x - dst_x; +} + +static int +clip_polygon_right(struct clip_context *ctx, const struct polygon8 *src, + GLfloat *dst_x, GLfloat *dst_y) +{ + enum path_transition trans; + int i; + + clip_context_prepare(ctx, src, dst_x, dst_y); + for (i = 0; i < src->n; i++) { + trans = path_transition_right_edge(ctx, src->x[i], src->y[i]); + clip_polygon_leftright(ctx, trans, src->x[i], src->y[i], + ctx->clip.x2); + } + return ctx->vertices.x - dst_x; +} + +static int +clip_polygon_top(struct clip_context *ctx, const struct polygon8 *src, + GLfloat *dst_x, GLfloat *dst_y) +{ + enum path_transition trans; + int i; + + clip_context_prepare(ctx, src, dst_x, dst_y); + for (i = 0; i < src->n; i++) { + trans = path_transition_top_edge(ctx, src->x[i], src->y[i]); + clip_polygon_topbottom(ctx, trans, src->x[i], src->y[i], + ctx->clip.y1); + } + return ctx->vertices.x - dst_x; +} + +static int +clip_polygon_bottom(struct clip_context *ctx, const struct polygon8 *src, + GLfloat *dst_x, GLfloat *dst_y) +{ + enum path_transition trans; + int i; + + clip_context_prepare(ctx, src, dst_x, dst_y); + for (i = 0; i < src->n; i++) { + trans = path_transition_bottom_edge(ctx, src->x[i], src->y[i]); + clip_polygon_topbottom(ctx, trans, src->x[i], src->y[i], + ctx->clip.y2); + } + return ctx->vertices.x - dst_x; +} + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) > (b)) ? (b) : (a)) +#define clip(x, a, b) min(max(x, a), b) + +/* + * Compute the boundary vertices of the intersection of the global coordinate + * aligned rectangle 'rect', and an arbitrary quadrilateral produced from + * 'surf_rect' when transformed from surface coordinates into global coordinates. + * The vertices are written to 'ex' and 'ey', and the return value is the + * number of vertices. Vertices are produced in clockwise winding order. + * Guarantees to produce either zero vertices, or 3-8 vertices with non-zero + * polygon area. + */ +static int +calculate_edges(struct weston_surface *es, pixman_box32_t *rect, + pixman_box32_t *surf_rect, GLfloat *ex, GLfloat *ey) +{ + struct polygon8 polygon; + struct clip_context ctx; + int i, n; + GLfloat min_x, max_x, min_y, max_y; + struct polygon8 surf = { + { surf_rect->x1, surf_rect->x2, surf_rect->x2, surf_rect->x1 }, + { surf_rect->y1, surf_rect->y1, surf_rect->y2, surf_rect->y2 }, + 4 + }; + + ctx.clip.x1 = rect->x1; + ctx.clip.y1 = rect->y1; + ctx.clip.x2 = rect->x2; + ctx.clip.y2 = rect->y2; + + /* transform surface to screen space: */ + for (i = 0; i < surf.n; i++) + weston_surface_to_global_float(es, surf.x[i], surf.y[i], + &surf.x[i], &surf.y[i]); + + /* find bounding box: */ + min_x = max_x = surf.x[0]; + min_y = max_y = surf.y[0]; + + for (i = 1; i < surf.n; i++) { + min_x = min(min_x, surf.x[i]); + max_x = max(max_x, surf.x[i]); + min_y = min(min_y, surf.y[i]); + max_y = max(max_y, surf.y[i]); + } + + /* First, simple bounding box check to discard early transformed + * surface rects that do not intersect with the clip region: + */ + if ((min_x >= ctx.clip.x2) || (max_x <= ctx.clip.x1) || + (min_y >= ctx.clip.y2) || (max_y <= ctx.clip.y1)) + return 0; + + /* Simple case, bounding box edges are parallel to surface edges, + * there will be only four edges. We just need to clip the surface + * vertices to the clip rect bounds: + */ + if (!es->transform.enabled) { + for (i = 0; i < surf.n; i++) { + ex[i] = clip(surf.x[i], ctx.clip.x1, ctx.clip.x2); + ey[i] = clip(surf.y[i], ctx.clip.y1, ctx.clip.y2); + } + return surf.n; + } + + /* Transformed case: use a general polygon clipping algorithm to + * clip the surface rectangle with each side of 'rect'. + * The algorithm is Sutherland-Hodgman, as explained in + * http://www.codeguru.com/cpp/misc/misc/graphics/article.php/c8965/Polygon-Clipping.htm + * but without looking at any of that code. + */ + polygon.n = clip_polygon_left(&ctx, &surf, polygon.x, polygon.y); + surf.n = clip_polygon_right(&ctx, &polygon, surf.x, surf.y); + polygon.n = clip_polygon_top(&ctx, &surf, polygon.x, polygon.y); + surf.n = clip_polygon_bottom(&ctx, &polygon, surf.x, surf.y); + + /* Get rid of duplicate vertices */ + ex[0] = surf.x[0]; + ey[0] = surf.y[0]; + n = 1; + for (i = 1; i < surf.n; i++) { + if (float_difference(ex[n - 1], surf.x[i]) == 0.0f && + float_difference(ey[n - 1], surf.y[i]) == 0.0f) + continue; + ex[n] = surf.x[i]; + ey[n] = surf.y[i]; + n++; + } + if (float_difference(ex[n - 1], surf.x[0]) == 0.0f && + float_difference(ey[n - 1], surf.y[0]) == 0.0f) + n--; + + if (n < 3) + return 0; + + return n; +} + + +/* ---------------------- copied ends -----------------------*/ + +static void +geometry_set_phi(struct geometry *g, float phi) +{ + g->phi = phi; + g->s = sin(phi); + g->c = cos(phi); +} + +static void +geometry_init(struct geometry *g) +{ + g->clip.x1 = -50; + g->clip.y1 = -50; + g->clip.x2 = -10; + g->clip.y2 = -10; + + g->surf.x1 = -20; + g->surf.y1 = -20; + g->surf.x2 = 20; + g->surf.y2 = 20; + + geometry_set_phi(g, 0.0); +} + +struct ui_state { + uint32_t button; + int down; + + int down_pos[2]; + struct geometry geometry; +}; + +struct cliptest { + struct window *window; + struct widget *widget; + struct display *display; + int fullscreen; + + struct ui_state ui; + + struct geometry geometry; + struct weston_surface surface; +}; + +static void +draw_polygon_closed(cairo_t *cr, GLfloat *x, GLfloat *y, int n) +{ + int i; + + cairo_move_to(cr, x[0], y[0]); + for (i = 1; i < n; i++) + cairo_line_to(cr, x[i], y[i]); + cairo_line_to(cr, x[0], y[0]); +} + +static void +draw_polygon_labels(cairo_t *cr, GLfloat *x, GLfloat *y, int n) +{ + char str[16]; + int i; + + for (i = 0; i < n; i++) { + snprintf(str, 16, "%d", i); + cairo_move_to(cr, x[i], y[i]); + cairo_show_text(cr, str); + } +} + +static void +draw_coordinates(cairo_t *cr, double ox, double oy, GLfloat *x, GLfloat *y, int n) +{ + char str[64]; + int i; + cairo_font_extents_t ext; + + cairo_font_extents(cr, &ext); + for (i = 0; i < n; i++) { + snprintf(str, 64, "%d: %14.9f, %14.9f", i, x[i], y[i]); + cairo_move_to(cr, ox, oy + ext.height * (i + 1)); + cairo_show_text(cr, str); + } +} + +static void +draw_box(cairo_t *cr, pixman_box32_t *box, struct weston_surface *surface) +{ + GLfloat x[4], y[4]; + + if (surface) { + weston_surface_to_global_float(surface, box->x1, box->y1, &x[0], &y[0]); + weston_surface_to_global_float(surface, box->x2, box->y1, &x[1], &y[1]); + weston_surface_to_global_float(surface, box->x2, box->y2, &x[2], &y[2]); + weston_surface_to_global_float(surface, box->x1, box->y2, &x[3], &y[3]); + } else { + x[0] = box->x1; y[0] = box->y1; + x[1] = box->x2; y[1] = box->y1; + x[2] = box->x2; y[2] = box->y2; + x[3] = box->x1; y[3] = box->y2; + } + + draw_polygon_closed(cr, x, y, 4); +} + +static void +draw_geometry(cairo_t *cr, struct weston_surface *surface, + GLfloat *ex, GLfloat *ey, int n) +{ + struct geometry *g = surface->geometry; + GLfloat cx, cy; + + draw_box(cr, &g->surf, surface); + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.4); + cairo_fill(cr); + weston_surface_to_global_float(surface, g->surf.x1 - 4, g->surf.y1 - 4, &cx, &cy); + cairo_arc(cr, cx, cy, 1.5, 0.0, 2.0 * M_PI); + if (surface->transform.enabled == 0) + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.8); + cairo_fill(cr); + + draw_box(cr, &g->clip, NULL); + cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 0.4); + cairo_fill(cr); + + draw_polygon_closed(cr, ex, ey, n); + cairo_set_source_rgb(cr, 0.0, 1.0, 0.0); + cairo_stroke(cr); + + cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 0.5); + draw_polygon_labels(cr, ex, ey, n); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct cliptest *cliptest = data; + struct geometry *g = cliptest->surface.geometry; + struct rectangle allocation; + cairo_t *cr; + cairo_surface_t *surface; + GLfloat ex[8]; + GLfloat ey[8]; + int n; + + n = calculate_edges(&cliptest->surface, &g->clip, &g->surf, ex, ey); + + widget_get_allocation(cliptest->widget, &allocation); + + surface = window_get_surface(cliptest->window); + cr = cairo_create(surface); + widget_get_allocation(cliptest->widget, &allocation); + cairo_rectangle(cr, allocation.x, allocation.y, + allocation.width, allocation.height); + cairo_clip(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_paint(cr); + + cairo_translate(cr, allocation.x, allocation.y); + cairo_set_line_width(cr, 1.0); + cairo_move_to(cr, allocation.width / 2.0, 0.0); + cairo_line_to(cr, allocation.width / 2.0, allocation.height); + cairo_move_to(cr, 0.0, allocation.height / 2.0); + cairo_line_to(cr, allocation.width, allocation.height / 2.0); + cairo_set_source_rgba(cr, 0.5, 0.5, 0.5, 1.0); + cairo_stroke(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_push_group(cr); + cairo_translate(cr, allocation.width / 2.0, + allocation.height / 2.0); + cairo_scale(cr, 4.0, 4.0); + cairo_set_line_width(cr, 0.5); + cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); + cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size(cr, 5.0); + draw_geometry(cr, &cliptest->surface, ex, ey, n); + cairo_pop_group_to_source(cr); + cairo_paint(cr); + + cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); + cairo_select_font_face(cr, "monospace", CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, 12.0); + draw_coordinates(cr, 10.0, 10.0, ex, ey, n); + + cairo_destroy(cr); + + cairo_surface_destroy(surface); +} + +static int +motion_handler(struct widget *widget, struct input *input, + uint32_t time, float x, float y, void *data) +{ + struct cliptest *cliptest = data; + struct ui_state *ui = &cliptest->ui; + struct geometry *ref = &ui->geometry; + struct geometry *geom = &cliptest->geometry; + float dx, dy; + + if (!ui->down) + return CURSOR_LEFT_PTR; + + dx = (x - ui->down_pos[0]) * 0.25; + dy = (y - ui->down_pos[1]) * 0.25; + + switch (ui->button) { + case BTN_LEFT: + geom->clip.x1 = ref->clip.x1 + dx; + geom->clip.y1 = ref->clip.y1 + dy; + /* fall through */ + case BTN_RIGHT: + geom->clip.x2 = ref->clip.x2 + dx; + geom->clip.y2 = ref->clip.y2 + dy; + break; + default: + return CURSOR_LEFT_PTR; + } + + widget_schedule_redraw(cliptest->widget); + return CURSOR_BLANK; +} + +static void +button_handler(struct widget *widget, struct input *input, + uint32_t time, uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct cliptest *cliptest = data; + struct ui_state *ui = &cliptest->ui; + + ui->button = button; + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + ui->down = 1; + input_get_position(input, &ui->down_pos[0], &ui->down_pos[1]); + } else { + ui->down = 0; + ui->geometry = cliptest->geometry; + } +} + +static void +axis_handler(struct widget *widget, struct input *input, uint32_t time, + uint32_t axis, wl_fixed_t value, void *data) +{ + struct cliptest *cliptest = data; + struct geometry *geom = &cliptest->geometry; + + if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) + return; + + geometry_set_phi(geom, geom->phi + + (M_PI / 12.0) * wl_fixed_to_double(value)); + cliptest->surface.transform.enabled = 1; + + widget_schedule_redraw(cliptest->widget); +} + +static void +key_handler(struct window *window, struct input *input, uint32_t time, + uint32_t key, uint32_t sym, + enum wl_keyboard_key_state state, void *data) +{ + struct cliptest *cliptest = data; + struct geometry *g = &cliptest->geometry; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + switch (sym) { + case XKB_KEY_Escape: + display_exit(cliptest->display); + return; + case XKB_KEY_w: + g->clip.y1 -= 1; + g->clip.y2 -= 1; + break; + case XKB_KEY_a: + g->clip.x1 -= 1; + g->clip.x2 -= 1; + break; + case XKB_KEY_s: + g->clip.y1 += 1; + g->clip.y2 += 1; + break; + case XKB_KEY_d: + g->clip.x1 += 1; + g->clip.x2 += 1; + break; + case XKB_KEY_i: + g->clip.y2 -= 1; + break; + case XKB_KEY_j: + g->clip.x2 -= 1; + break; + case XKB_KEY_k: + g->clip.y2 += 1; + break; + case XKB_KEY_l: + g->clip.x2 += 1; + break; + case XKB_KEY_n: + geometry_set_phi(g, g->phi + (M_PI / 24.0)); + cliptest->surface.transform.enabled = 1; + break; + case XKB_KEY_m: + geometry_set_phi(g, g->phi - (M_PI / 24.0)); + cliptest->surface.transform.enabled = 1; + break; + case XKB_KEY_r: + geometry_set_phi(g, 0.0); + cliptest->surface.transform.enabled = 0; + break; + default: + return; + } + + widget_schedule_redraw(cliptest->widget); +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct cliptest *cliptest = data; + + window_schedule_redraw(cliptest->window); +} + +static void +fullscreen_handler(struct window *window, void *data) +{ + struct cliptest *cliptest = data; + + cliptest->fullscreen ^= 1; + window_set_fullscreen(window, cliptest->fullscreen); +} + +static struct cliptest * +cliptest_create(struct display *display) +{ + struct cliptest *cliptest; + + cliptest = xzalloc(sizeof *cliptest); + cliptest->surface.geometry = &cliptest->geometry; + cliptest->surface.transform.enabled = 0; + geometry_init(&cliptest->geometry); + geometry_init(&cliptest->ui.geometry); + + cliptest->window = window_create(display); + cliptest->widget = frame_create(cliptest->window, cliptest); + window_set_title(cliptest->window, "cliptest"); + cliptest->display = display; + + window_set_user_data(cliptest->window, cliptest); + widget_set_redraw_handler(cliptest->widget, redraw_handler); + widget_set_button_handler(cliptest->widget, button_handler); + widget_set_motion_handler(cliptest->widget, motion_handler); + widget_set_axis_handler(cliptest->widget, axis_handler); + + window_set_keyboard_focus_handler(cliptest->window, + keyboard_focus_handler); + window_set_key_handler(cliptest->window, key_handler); + window_set_fullscreen_handler(cliptest->window, fullscreen_handler); + + /* set minimum size */ + widget_schedule_resize(cliptest->widget, 200, 100); + + /* set current size */ + widget_schedule_resize(cliptest->widget, 500, 400); + + return cliptest; +} + +static struct timespec begin_time; + +static void +reset_timer(void) +{ + clock_gettime(CLOCK_MONOTONIC, &begin_time); +} + +static double +read_timer(void) +{ + struct timespec t; + + clock_gettime(CLOCK_MONOTONIC, &t); + return (double)(t.tv_sec - begin_time.tv_sec) + + 1e-9 * (t.tv_nsec - begin_time.tv_nsec); +} + +static int +benchmark(void) +{ + struct weston_surface surface; + struct geometry geom; + GLfloat ex[8], ey[8]; + int i; + double t; + const int N = 1000000; + + geom.clip.x1 = -19; + geom.clip.y1 = -19; + geom.clip.x2 = 19; + geom.clip.y2 = 19; + + geom.surf.x1 = -20; + geom.surf.y1 = -20; + geom.surf.x2 = 20; + geom.surf.y2 = 20; + + geometry_set_phi(&geom, 0.0); + + surface.transform.enabled = 1; + surface.geometry = &geom; + + reset_timer(); + for (i = 0; i < N; i++) { + geometry_set_phi(&geom, (float)i / 360.0f); + calculate_edges(&surface, &geom.clip, &geom.surf, ex, ey); + } + t = read_timer(); + + printf("%d calls took %g s, average %g us/call\n", N, t, t / N * 1e6); + + return 0; +} + +int +main(int argc, char *argv[]) +{ + struct display *d; + struct cliptest *cliptest; + + if (argc > 1) + return benchmark(); + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + cliptest = cliptest_create(d); + display_run(d); + + widget_destroy(cliptest->widget); + window_destroy(cliptest->window); + free(cliptest); + + return 0; +} diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c new file mode 100644 index 00000000..599c0a5b --- /dev/null +++ b/clients/desktop-shell.c @@ -0,0 +1,1323 @@ +/* + * Copyright © 2011 Kristian Høgsberg + * Copyright © 2011 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "window.h" +#include "../shared/cairo-util.h" +#include "../shared/config-parser.h" + +#include "desktop-shell-client-protocol.h" + +extern char **environ; /* defined by libc */ + +struct desktop { + struct display *display; + struct desktop_shell *shell; + uint32_t interface_version; + struct unlock_dialog *unlock_dialog; + struct task unlock_task; + struct wl_list outputs; + + struct window *grab_window; + struct widget *grab_widget; + + struct weston_config *config; + int locking; + + enum cursor_type grab_cursor; + + int painted; +}; + +struct surface { + void (*configure)(void *data, + struct desktop_shell *desktop_shell, + uint32_t edges, struct window *window, + int32_t width, int32_t height); +}; + +struct panel { + struct surface base; + struct window *window; + struct widget *widget; + struct wl_list launcher_list; + struct panel_clock *clock; + int painted; + uint32_t color; +}; + +struct background { + struct surface base; + struct window *window; + struct widget *widget; + int painted; + + char *image; + int type; + uint32_t color; +}; + +struct output { + struct wl_output *output; + struct wl_list link; + + struct panel *panel; + struct background *background; +}; + +struct panel_launcher { + struct widget *widget; + struct panel *panel; + cairo_surface_t *icon; + int focused, pressed; + char *path; + struct wl_list link; + struct wl_array envp; + struct wl_array argv; +}; + +struct panel_clock { + struct widget *widget; + struct panel *panel; + struct task clock_task; + int clock_fd; +}; + +struct unlock_dialog { + struct window *window; + struct widget *widget; + struct widget *button; + int button_focused; + int closing; + struct desktop *desktop; +}; + +static void +panel_add_launchers(struct panel *panel, struct desktop *desktop); + +static void +sigchild_handler(int s) +{ + int status; + pid_t pid; + + while (pid = waitpid(-1, &status, WNOHANG), pid > 0) + fprintf(stderr, "child %d exited\n", pid); +} + +static void +menu_func(struct window *window, int index, void *data) +{ + printf("Selected index %d from a panel menu.\n", index); +} + +static void +show_menu(struct panel *panel, struct input *input, uint32_t time) +{ + int32_t x, y; + static const char *entries[] = { + "Roy", "Pris", "Leon", "Zhora" + }; + + input_get_position(input, &x, &y); + window_show_menu(window_get_display(panel->window), + input, time, panel->window, + x - 10, y - 10, menu_func, entries, 4); +} + +static int +is_desktop_painted(struct desktop *desktop) +{ + struct output *output; + + wl_list_for_each(output, &desktop->outputs, link) { + if (output->panel && !output->panel->painted) + return 0; + if (output->background && !output->background->painted) + return 0; + } + + return 1; +} + +static void +check_desktop_ready(struct window *window) +{ + struct display *display; + struct desktop *desktop; + + display = window_get_display(window); + desktop = display_get_user_data(display); + + if (!desktop->painted && is_desktop_painted(desktop)) { + desktop->painted = 1; + + if (desktop->interface_version >= 2) + desktop_shell_desktop_ready(desktop->shell); + } +} + +static void +panel_launcher_activate(struct panel_launcher *widget) +{ + char **argv; + pid_t pid; + + pid = fork(); + if (pid < 0) { + fprintf(stderr, "fork failed: %m\n"); + return; + } + + if (pid) + return; + + argv = widget->argv.data; + if (execve(argv[0], argv, widget->envp.data) < 0) { + fprintf(stderr, "execl '%s' failed: %m\n", argv[0]); + exit(1); + } +} + +static void +panel_launcher_redraw_handler(struct widget *widget, void *data) +{ + struct panel_launcher *launcher = data; + struct rectangle allocation; + cairo_t *cr; + + cr = widget_cairo_create(launcher->panel->widget); + + widget_get_allocation(widget, &allocation); + if (launcher->pressed) { + allocation.x++; + allocation.y++; + } + + cairo_set_source_surface(cr, launcher->icon, + allocation.x, allocation.y); + cairo_paint(cr); + + if (launcher->focused) { + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.4); + cairo_mask_surface(cr, launcher->icon, + allocation.x, allocation.y); + } + + cairo_destroy(cr); +} + +static int +panel_launcher_motion_handler(struct widget *widget, struct input *input, + uint32_t time, float x, float y, void *data) +{ + struct panel_launcher *launcher = data; + + widget_set_tooltip(widget, basename((char *)launcher->path), x, y); + + return CURSOR_LEFT_PTR; +} + +static void +set_hex_color(cairo_t *cr, uint32_t color) +{ + cairo_set_source_rgba(cr, + ((color >> 16) & 0xff) / 255.0, + ((color >> 8) & 0xff) / 255.0, + ((color >> 0) & 0xff) / 255.0, + ((color >> 24) & 0xff) / 255.0); +} + +static void +panel_redraw_handler(struct widget *widget, void *data) +{ + cairo_surface_t *surface; + cairo_t *cr; + struct panel *panel = data; + + cr = widget_cairo_create(panel->widget); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + set_hex_color(cr, panel->color); + cairo_paint(cr); + + cairo_destroy(cr); + surface = window_get_surface(panel->window); + cairo_surface_destroy(surface); + panel->painted = 1; + check_desktop_ready(panel->window); +} + +static int +panel_launcher_enter_handler(struct widget *widget, struct input *input, + float x, float y, void *data) +{ + struct panel_launcher *launcher = data; + + launcher->focused = 1; + widget_schedule_redraw(widget); + + return CURSOR_LEFT_PTR; +} + +static void +panel_launcher_leave_handler(struct widget *widget, + struct input *input, void *data) +{ + struct panel_launcher *launcher = data; + + launcher->focused = 0; + widget_destroy_tooltip(widget); + widget_schedule_redraw(widget); +} + +static void +panel_launcher_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct panel_launcher *launcher; + + launcher = widget_get_user_data(widget); + widget_schedule_redraw(widget); + if (state == WL_POINTER_BUTTON_STATE_RELEASED) + panel_launcher_activate(launcher); + +} + +static void +panel_launcher_touch_down_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float x, float y, void *data) +{ + struct panel_launcher *launcher; + + launcher = widget_get_user_data(widget); + launcher->focused = 1; + widget_schedule_redraw(widget); +} + +static void +panel_launcher_touch_up_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + void *data) +{ + struct panel_launcher *launcher; + + launcher = widget_get_user_data(widget); + launcher->focused = 0; + widget_schedule_redraw(widget); + panel_launcher_activate(launcher); +} + +static void +clock_func(struct task *task, uint32_t events) +{ + struct panel_clock *clock = + container_of(task, struct panel_clock, clock_task); + uint64_t exp; + + if (read(clock->clock_fd, &exp, sizeof exp) != sizeof exp) + abort(); + widget_schedule_redraw(clock->widget); +} + +static void +panel_clock_redraw_handler(struct widget *widget, void *data) +{ + struct panel_clock *clock = data; + cairo_t *cr; + struct rectangle allocation; + cairo_text_extents_t extents; + cairo_font_extents_t font_extents; + time_t rawtime; + struct tm * timeinfo; + char string[128]; + + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(string, sizeof string, "%a %b %d, %I:%M %p", timeinfo); + + widget_get_allocation(widget, &allocation); + if (allocation.width == 0) + return; + + cr = widget_cairo_create(clock->panel->widget); + cairo_select_font_face(cr, "sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, 14); + cairo_text_extents(cr, string, &extents); + cairo_font_extents (cr, &font_extents); + cairo_move_to(cr, allocation.x + 5, + allocation.y + 3 * (allocation.height >> 2) + 1); + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_show_text(cr, string); + cairo_move_to(cr, allocation.x + 4, + allocation.y + 3 * (allocation.height >> 2)); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_show_text(cr, string); + cairo_destroy(cr); +} + +static int +clock_timer_reset(struct panel_clock *clock) +{ + struct itimerspec its; + + its.it_interval.tv_sec = 60; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = 60; + its.it_value.tv_nsec = 0; + if (timerfd_settime(clock->clock_fd, 0, &its, NULL) < 0) { + fprintf(stderr, "could not set timerfd\n: %m"); + return -1; + } + + return 0; +} + +static void +panel_destroy_clock(struct panel_clock *clock) +{ + widget_destroy(clock->widget); + + close(clock->clock_fd); + + free(clock); +} + +static void +panel_add_clock(struct panel *panel) +{ + struct panel_clock *clock; + int timerfd; + + timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + if (timerfd < 0) { + fprintf(stderr, "could not create timerfd\n: %m"); + return; + } + + clock = xzalloc(sizeof *clock); + clock->panel = panel; + panel->clock = clock; + clock->clock_fd = timerfd; + + clock->clock_task.run = clock_func; + display_watch_fd(window_get_display(panel->window), clock->clock_fd, + EPOLLIN, &clock->clock_task); + clock_timer_reset(clock); + + clock->widget = widget_add_widget(panel->widget, clock); + widget_set_redraw_handler(clock->widget, panel_clock_redraw_handler); +} + +static void +panel_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct panel *panel = data; + + if (button == BTN_RIGHT && state == WL_POINTER_BUTTON_STATE_PRESSED) + show_menu(panel, input, time); +} + +static void +panel_resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct panel_launcher *launcher; + struct panel *panel = data; + int x, y, w, h; + + x = 10; + y = 16; + wl_list_for_each(launcher, &panel->launcher_list, link) { + w = cairo_image_surface_get_width(launcher->icon); + h = cairo_image_surface_get_height(launcher->icon); + widget_set_allocation(launcher->widget, + x, y - h / 2, w + 1, h + 1); + x += w + 10; + } + h=20; + w=170; + + if (panel->clock) + widget_set_allocation(panel->clock->widget, + width - w - 8, y - h / 2, w + 1, h + 1); +} + +static void +panel_configure(void *data, + struct desktop_shell *desktop_shell, + uint32_t edges, struct window *window, + int32_t width, int32_t height) +{ + struct surface *surface = window_get_user_data(window); + struct panel *panel = container_of(surface, struct panel, base); + + window_schedule_resize(panel->window, width, 32); +} + +static void +panel_destroy_launcher(struct panel_launcher *launcher) +{ + wl_array_release(&launcher->argv); + wl_array_release(&launcher->envp); + + free(launcher->path); + + cairo_surface_destroy(launcher->icon); + + widget_destroy(launcher->widget); + wl_list_remove(&launcher->link); + + free(launcher); +} + +static void +panel_destroy(struct panel *panel) +{ + struct panel_launcher *tmp; + struct panel_launcher *launcher; + + panel_destroy_clock(panel->clock); + + wl_list_for_each_safe(launcher, tmp, &panel->launcher_list, link) + panel_destroy_launcher(launcher); + + widget_destroy(panel->widget); + window_destroy(panel->window); + + free(panel); +} + +static struct panel * +panel_create(struct desktop *desktop) +{ + struct panel *panel; + struct weston_config_section *s; + + panel = xzalloc(sizeof *panel); + + panel->base.configure = panel_configure; + panel->window = window_create_custom(desktop->display); + panel->widget = window_add_widget(panel->window, panel); + wl_list_init(&panel->launcher_list); + + window_set_title(panel->window, "panel"); + window_set_user_data(panel->window, panel); + + widget_set_redraw_handler(panel->widget, panel_redraw_handler); + widget_set_resize_handler(panel->widget, panel_resize_handler); + widget_set_button_handler(panel->widget, panel_button_handler); + + panel_add_clock(panel); + + s = weston_config_get_section(desktop->config, "shell", NULL, NULL); + weston_config_section_get_uint(s, "panel-color", + &panel->color, 0xaa000000); + + panel_add_launchers(panel, desktop); + + return panel; +} + +static cairo_surface_t * +load_icon_or_fallback(const char *icon) +{ + cairo_surface_t *surface = cairo_image_surface_create_from_png(icon); + cairo_status_t status; + cairo_t *cr; + + status = cairo_surface_status(surface); + if (status == CAIRO_STATUS_SUCCESS) + return surface; + + cairo_surface_destroy(surface); + fprintf(stderr, "ERROR loading icon from file '%s', error: '%s'\n", + icon, cairo_status_to_string(status)); + + /* draw fallback icon */ + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + 20, 20); + cr = cairo_create(surface); + + cairo_set_source_rgba(cr, 0.8, 0.8, 0.8, 1); + cairo_paint(cr); + + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); + cairo_rectangle(cr, 0, 0, 20, 20); + cairo_move_to(cr, 4, 4); + cairo_line_to(cr, 16, 16); + cairo_move_to(cr, 4, 16); + cairo_line_to(cr, 16, 4); + cairo_stroke(cr); + + cairo_destroy(cr); + + return surface; +} + +static void +panel_add_launcher(struct panel *panel, const char *icon, const char *path) +{ + struct panel_launcher *launcher; + char *start, *p, *eq, **ps; + int i, j, k; + + launcher = xzalloc(sizeof *launcher); + launcher->icon = load_icon_or_fallback(icon); + launcher->path = strdup(path); + + wl_array_init(&launcher->envp); + wl_array_init(&launcher->argv); + for (i = 0; environ[i]; i++) { + ps = wl_array_add(&launcher->envp, sizeof *ps); + *ps = environ[i]; + } + j = 0; + + start = launcher->path; + while (*start) { + for (p = start, eq = NULL; *p && !isspace(*p); p++) + if (*p == '=') + eq = p; + + if (eq && j == 0) { + ps = launcher->envp.data; + for (k = 0; k < i; k++) + if (strncmp(ps[k], start, eq - start) == 0) { + ps[k] = start; + break; + } + if (k == i) { + ps = wl_array_add(&launcher->envp, sizeof *ps); + *ps = start; + i++; + } + } else { + ps = wl_array_add(&launcher->argv, sizeof *ps); + *ps = start; + j++; + } + + while (*p && isspace(*p)) + *p++ = '\0'; + + start = p; + } + + ps = wl_array_add(&launcher->envp, sizeof *ps); + *ps = NULL; + ps = wl_array_add(&launcher->argv, sizeof *ps); + *ps = NULL; + + launcher->panel = panel; + wl_list_insert(panel->launcher_list.prev, &launcher->link); + + launcher->widget = widget_add_widget(panel->widget, launcher); + widget_set_enter_handler(launcher->widget, + panel_launcher_enter_handler); + widget_set_leave_handler(launcher->widget, + panel_launcher_leave_handler); + widget_set_button_handler(launcher->widget, + panel_launcher_button_handler); + widget_set_touch_down_handler(launcher->widget, + panel_launcher_touch_down_handler); + widget_set_touch_up_handler(launcher->widget, + panel_launcher_touch_up_handler); + widget_set_redraw_handler(launcher->widget, + panel_launcher_redraw_handler); + widget_set_motion_handler(launcher->widget, + panel_launcher_motion_handler); +} + +enum { + BACKGROUND_SCALE, + BACKGROUND_SCALE_CROP, + BACKGROUND_TILE +}; + +static void +background_draw(struct widget *widget, void *data) +{ + struct background *background = data; + cairo_surface_t *surface, *image; + cairo_pattern_t *pattern; + cairo_matrix_t matrix; + cairo_t *cr; + double im_w, im_h; + double sx, sy, s; + double tx, ty; + struct rectangle allocation; + struct display *display; + struct wl_region *opaque; + + surface = window_get_surface(background->window); + + cr = widget_cairo_create(background->widget); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.2, 1.0); + cairo_paint(cr); + + widget_get_allocation(widget, &allocation); + image = NULL; + if (background->image) + image = load_cairo_surface(background->image); + + if (image && background->type != -1) { + im_w = cairo_image_surface_get_width(image); + im_h = cairo_image_surface_get_height(image); + sx = im_w / allocation.width; + sy = im_h / allocation.height; + + pattern = cairo_pattern_create_for_surface(image); + + switch (background->type) { + case BACKGROUND_SCALE: + cairo_matrix_init_scale(&matrix, sx, sy); + cairo_pattern_set_matrix(pattern, &matrix); + break; + case BACKGROUND_SCALE_CROP: + s = (sx < sy) ? sx : sy; + /* align center */ + tx = (im_w - s * allocation.width) * 0.5; + ty = (im_h - s * allocation.height) * 0.5; + cairo_matrix_init_translate(&matrix, tx, ty); + cairo_matrix_scale(&matrix, s, s); + cairo_pattern_set_matrix(pattern, &matrix); + break; + case BACKGROUND_TILE: + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); + break; + } + + cairo_set_source(cr, pattern); + cairo_pattern_destroy (pattern); + cairo_surface_destroy(image); + } else { + set_hex_color(cr, background->color); + } + + cairo_paint(cr); + cairo_destroy(cr); + cairo_surface_destroy(surface); + + display = window_get_display(background->window); + opaque = wl_compositor_create_region(display_get_compositor(display)); + wl_region_add(opaque, allocation.x, allocation.y, + allocation.width, allocation.height); + wl_surface_set_opaque_region(window_get_wl_surface(background->window), opaque); + wl_region_destroy(opaque); + + background->painted = 1; + check_desktop_ready(background->window); +} + +static void +background_configure(void *data, + struct desktop_shell *desktop_shell, + uint32_t edges, struct window *window, + int32_t width, int32_t height) +{ + struct background *background = + (struct background *) window_get_user_data(window); + + widget_schedule_resize(background->widget, width, height); +} + +static void +unlock_dialog_redraw_handler(struct widget *widget, void *data) +{ + struct unlock_dialog *dialog = data; + struct rectangle allocation; + cairo_surface_t *surface; + cairo_t *cr; + cairo_pattern_t *pat; + double cx, cy, r, f; + + cr = widget_cairo_create(widget); + + widget_get_allocation(dialog->widget, &allocation); + cairo_rectangle(cr, allocation.x, allocation.y, + allocation.width, allocation.height); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0, 0, 0, 0.6); + cairo_fill(cr); + + cairo_translate(cr, allocation.x, allocation.y); + if (dialog->button_focused) + f = 1.0; + else + f = 0.7; + + cx = allocation.width / 2.0; + cy = allocation.height / 2.0; + r = (cx < cy ? cx : cy) * 0.4; + pat = cairo_pattern_create_radial(cx, cy, r * 0.7, cx, cy, r); + cairo_pattern_add_color_stop_rgb(pat, 0.0, 0, 0.86 * f, 0); + cairo_pattern_add_color_stop_rgb(pat, 0.85, 0.2 * f, f, 0.2 * f); + cairo_pattern_add_color_stop_rgb(pat, 1.0, 0, 0.86 * f, 0); + cairo_set_source(cr, pat); + cairo_pattern_destroy(pat); + cairo_arc(cr, cx, cy, r, 0.0, 2.0 * M_PI); + cairo_fill(cr); + + widget_set_allocation(dialog->button, + allocation.x + cx - r, + allocation.y + cy - r, 2 * r, 2 * r); + + cairo_destroy(cr); + + surface = window_get_surface(dialog->window); + cairo_surface_destroy(surface); +} + +static void +unlock_dialog_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct unlock_dialog *dialog = data; + struct desktop *desktop = dialog->desktop; + + if (button == BTN_LEFT) { + if (state == WL_POINTER_BUTTON_STATE_RELEASED && + !dialog->closing) { + display_defer(desktop->display, &desktop->unlock_task); + dialog->closing = 1; + } + } +} + +static void +unlock_dialog_touch_down_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float x, float y, void *data) +{ + struct unlock_dialog *dialog = data; + + dialog->button_focused = 1; + widget_schedule_redraw(widget); +} + +static void +unlock_dialog_touch_up_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + void *data) +{ + struct unlock_dialog *dialog = data; + struct desktop *desktop = dialog->desktop; + + dialog->button_focused = 0; + widget_schedule_redraw(widget); + display_defer(desktop->display, &desktop->unlock_task); + dialog->closing = 1; +} + +static void +unlock_dialog_keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + window_schedule_redraw(window); +} + +static int +unlock_dialog_widget_enter_handler(struct widget *widget, + struct input *input, + float x, float y, void *data) +{ + struct unlock_dialog *dialog = data; + + dialog->button_focused = 1; + widget_schedule_redraw(widget); + + return CURSOR_LEFT_PTR; +} + +static void +unlock_dialog_widget_leave_handler(struct widget *widget, + struct input *input, void *data) +{ + struct unlock_dialog *dialog = data; + + dialog->button_focused = 0; + widget_schedule_redraw(widget); +} + +static struct unlock_dialog * +unlock_dialog_create(struct desktop *desktop) +{ + struct display *display = desktop->display; + struct unlock_dialog *dialog; + + dialog = xzalloc(sizeof *dialog); + + dialog->window = window_create_custom(display); + dialog->widget = frame_create(dialog->window, dialog); + window_set_title(dialog->window, "Unlock your desktop"); + + window_set_user_data(dialog->window, dialog); + window_set_keyboard_focus_handler(dialog->window, + unlock_dialog_keyboard_focus_handler); + dialog->button = widget_add_widget(dialog->widget, dialog); + widget_set_redraw_handler(dialog->widget, + unlock_dialog_redraw_handler); + widget_set_enter_handler(dialog->button, + unlock_dialog_widget_enter_handler); + widget_set_leave_handler(dialog->button, + unlock_dialog_widget_leave_handler); + widget_set_button_handler(dialog->button, + unlock_dialog_button_handler); + widget_set_touch_down_handler(dialog->button, + unlock_dialog_touch_down_handler); + widget_set_touch_up_handler(dialog->button, + unlock_dialog_touch_up_handler); + + desktop_shell_set_lock_surface(desktop->shell, + window_get_wl_surface(dialog->window)); + + window_schedule_resize(dialog->window, 260, 230); + + return dialog; +} + +static void +unlock_dialog_destroy(struct unlock_dialog *dialog) +{ + window_destroy(dialog->window); + free(dialog); +} + +static void +unlock_dialog_finish(struct task *task, uint32_t events) +{ + struct desktop *desktop = + container_of(task, struct desktop, unlock_task); + + desktop_shell_unlock(desktop->shell); + unlock_dialog_destroy(desktop->unlock_dialog); + desktop->unlock_dialog = NULL; +} + +static void +desktop_shell_configure(void *data, + struct desktop_shell *desktop_shell, + uint32_t edges, + struct wl_surface *surface, + int32_t width, int32_t height) +{ + struct window *window = wl_surface_get_user_data(surface); + struct surface *s = window_get_user_data(window); + + s->configure(data, desktop_shell, edges, window, width, height); +} + +static void +desktop_shell_prepare_lock_surface(void *data, + struct desktop_shell *desktop_shell) +{ + struct desktop *desktop = data; + + if (!desktop->locking) { + desktop_shell_unlock(desktop->shell); + return; + } + + if (!desktop->unlock_dialog) { + desktop->unlock_dialog = unlock_dialog_create(desktop); + desktop->unlock_dialog->desktop = desktop; + } +} + +static void +desktop_shell_grab_cursor(void *data, + struct desktop_shell *desktop_shell, + uint32_t cursor) +{ + struct desktop *desktop = data; + + switch (cursor) { + case DESKTOP_SHELL_CURSOR_NONE: + desktop->grab_cursor = CURSOR_BLANK; + break; + case DESKTOP_SHELL_CURSOR_BUSY: + desktop->grab_cursor = CURSOR_WATCH; + break; + case DESKTOP_SHELL_CURSOR_MOVE: + desktop->grab_cursor = CURSOR_DRAGGING; + break; + case DESKTOP_SHELL_CURSOR_RESIZE_TOP: + desktop->grab_cursor = CURSOR_TOP; + break; + case DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM: + desktop->grab_cursor = CURSOR_BOTTOM; + break; + case DESKTOP_SHELL_CURSOR_RESIZE_LEFT: + desktop->grab_cursor = CURSOR_LEFT; + break; + case DESKTOP_SHELL_CURSOR_RESIZE_RIGHT: + desktop->grab_cursor = CURSOR_RIGHT; + break; + case DESKTOP_SHELL_CURSOR_RESIZE_TOP_LEFT: + desktop->grab_cursor = CURSOR_TOP_LEFT; + break; + case DESKTOP_SHELL_CURSOR_RESIZE_TOP_RIGHT: + desktop->grab_cursor = CURSOR_TOP_RIGHT; + break; + case DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM_LEFT: + desktop->grab_cursor = CURSOR_BOTTOM_LEFT; + break; + case DESKTOP_SHELL_CURSOR_RESIZE_BOTTOM_RIGHT: + desktop->grab_cursor = CURSOR_BOTTOM_RIGHT; + break; + case DESKTOP_SHELL_CURSOR_ARROW: + default: + desktop->grab_cursor = CURSOR_LEFT_PTR; + } +} + +static const struct desktop_shell_listener listener = { + desktop_shell_configure, + desktop_shell_prepare_lock_surface, + desktop_shell_grab_cursor +}; + +static void +background_destroy(struct background *background) +{ + widget_destroy(background->widget); + window_destroy(background->window); + + free(background->image); + free(background); +} + +static struct background * +background_create(struct desktop *desktop) +{ + struct background *background; + struct weston_config_section *s; + char *type; + + background = xzalloc(sizeof *background); + background->base.configure = background_configure; + background->window = window_create_custom(desktop->display); + background->widget = window_add_widget(background->window, background); + window_set_user_data(background->window, background); + widget_set_redraw_handler(background->widget, background_draw); + window_set_preferred_format(background->window, + WINDOW_PREFERRED_FORMAT_RGB565); + + s = weston_config_get_section(desktop->config, "shell", NULL, NULL); + weston_config_section_get_string(s, "background-image", + &background->image, + DATADIR "/weston/pattern.png"); + weston_config_section_get_uint(s, "background-color", + &background->color, 0xff002244); + + weston_config_section_get_string(s, "background-type", + &type, "tile"); + if (strcmp(type, "scale") == 0) { + background->type = BACKGROUND_SCALE; + } else if (strcmp(type, "scale-crop") == 0) { + background->type = BACKGROUND_SCALE_CROP; + } else if (strcmp(type, "tile") == 0) { + background->type = BACKGROUND_TILE; + } else { + background->type = -1; + fprintf(stderr, "invalid background-type: %s\n", + type); + } + + free(type); + + return background; +} + +static int +grab_surface_enter_handler(struct widget *widget, struct input *input, + float x, float y, void *data) +{ + struct desktop *desktop = data; + + return desktop->grab_cursor; +} + +static void +grab_surface_destroy(struct desktop *desktop) +{ + widget_destroy(desktop->grab_widget); + window_destroy(desktop->grab_window); +} + +static void +grab_surface_create(struct desktop *desktop) +{ + struct wl_surface *s; + + desktop->grab_window = window_create_custom(desktop->display); + window_set_user_data(desktop->grab_window, desktop); + + s = window_get_wl_surface(desktop->grab_window); + desktop_shell_set_grab_surface(desktop->shell, s); + + desktop->grab_widget = + window_add_widget(desktop->grab_window, desktop); + /* We set the allocation to 1x1 at 0,0 so the fake enter event + * at 0,0 will go to this widget. */ + widget_set_allocation(desktop->grab_widget, 0, 0, 1, 1); + + widget_set_enter_handler(desktop->grab_widget, + grab_surface_enter_handler); +} + +static void +output_destroy(struct output *output) +{ + background_destroy(output->background); + panel_destroy(output->panel); + wl_output_destroy(output->output); + wl_list_remove(&output->link); + + free(output); +} + +static void +desktop_destroy_outputs(struct desktop *desktop) +{ + struct output *tmp; + struct output *output; + + wl_list_for_each_safe(output, tmp, &desktop->outputs, link) + output_destroy(output); +} + +static void +output_handle_geometry(void *data, + struct wl_output *wl_output, + int x, int y, + int physical_width, + int physical_height, + int subpixel, + const char *make, + const char *model, + int transform) +{ + struct output *output = data; + + window_set_buffer_transform(output->panel->window, transform); + window_set_buffer_transform(output->background->window, transform); +} + +static void +output_handle_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int width, + int height, + int refresh) +{ +} + +static void +output_handle_done(void *data, + struct wl_output *wl_output) +{ +} + +static void +output_handle_scale(void *data, + struct wl_output *wl_output, + int32_t scale) +{ + struct output *output = data; + + window_set_buffer_scale(output->panel->window, scale); + window_set_buffer_scale(output->background->window, scale); +} + +static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode, + output_handle_done, + output_handle_scale +}; + +static void +output_init(struct output *output, struct desktop *desktop) +{ + struct wl_surface *surface; + + output->panel = panel_create(desktop); + surface = window_get_wl_surface(output->panel->window); + desktop_shell_set_panel(desktop->shell, + output->output, surface); + + output->background = background_create(desktop); + surface = window_get_wl_surface(output->background->window); + desktop_shell_set_background(desktop->shell, + output->output, surface); +} + +static void +create_output(struct desktop *desktop, uint32_t id) +{ + struct output *output; + + output = calloc(1, sizeof *output); + if (!output) + return; + + output->output = + display_bind(desktop->display, id, &wl_output_interface, 2); + + wl_output_add_listener(output->output, &output_listener, output); + + wl_list_insert(&desktop->outputs, &output->link); + + /* On start up we may process an output global before the shell global + * in which case we can't create the panel and background just yet */ + if (desktop->shell) + output_init(output, desktop); +} + +static void +global_handler(struct display *display, uint32_t id, + const char *interface, uint32_t version, void *data) +{ + struct desktop *desktop = data; + + if (!strcmp(interface, "desktop_shell")) { + desktop->interface_version = (version < 2) ? version : 2; + desktop->shell = display_bind(desktop->display, + id, &desktop_shell_interface, + desktop->interface_version); + desktop_shell_add_listener(desktop->shell, &listener, desktop); + } else if (!strcmp(interface, "wl_output")) { + create_output(desktop, id); + } +} + +static void +panel_add_launchers(struct panel *panel, struct desktop *desktop) +{ + struct weston_config_section *s; + char *icon, *path; + const char *name; + int count; + + count = 0; + s = NULL; + while (weston_config_next_section(desktop->config, &s, &name)) { + if (strcmp(name, "launcher") != 0) + continue; + + weston_config_section_get_string(s, "icon", &icon, NULL); + weston_config_section_get_string(s, "path", &path, NULL); + + if (icon != NULL && path != NULL) { + panel_add_launcher(panel, icon, path); + count++; + } else { + fprintf(stderr, "invalid launcher section\n"); + } + + free(icon); + free(path); + } + + if (count == 0) { + /* add default launcher */ + panel_add_launcher(panel, + DATADIR "/weston/terminal.png", + BINDIR "/weston-terminal"); + } +} + +int main(int argc, char *argv[]) +{ + struct desktop desktop = { 0 }; + struct output *output; + struct weston_config_section *s; + + desktop.unlock_task.run = unlock_dialog_finish; + wl_list_init(&desktop.outputs); + + desktop.config = weston_config_parse("weston.ini"); + s = weston_config_get_section(desktop.config, "shell", NULL, NULL); + weston_config_section_get_bool(s, "locking", &desktop.locking, 1); + + desktop.display = display_create(&argc, argv); + if (desktop.display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + display_set_user_data(desktop.display, &desktop); + display_set_global_handler(desktop.display, global_handler); + + /* Create panel and background for outputs processed before the shell + * global interface was processed */ + wl_list_for_each(output, &desktop.outputs, link) + if (!output->panel) + output_init(output, &desktop); + + grab_surface_create(&desktop); + + signal(SIGCHLD, sigchild_handler); + + display_run(desktop.display); + + /* Cleanup */ + grab_surface_destroy(&desktop); + desktop_destroy_outputs(&desktop); + if (desktop.unlock_dialog) + unlock_dialog_destroy(desktop.unlock_dialog); + desktop_shell_destroy(desktop.shell); + display_destroy(desktop.display); + + return 0; +} diff --git a/clients/dnd.c b/clients/dnd.c new file mode 100644 index 00000000..19cc243c --- /dev/null +++ b/clients/dnd.c @@ -0,0 +1,648 @@ +/* + * Copyright © 2010 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" +#include "../shared/cairo-util.h" + +struct dnd_drag; + +struct dnd { + struct window *window; + struct widget *widget; + struct display *display; + uint32_t key; + struct item *items[16]; + int self_only; + struct dnd_drag *current_drag; +}; + +struct dnd_drag { + cairo_surface_t *translucent; + cairo_surface_t *opaque; + int hotspot_x, hotspot_y; + struct dnd *dnd; + struct input *input; + uint32_t time; + struct item *item; + int x_offset, y_offset; + int width, height; + const char *mime_type; + + struct wl_surface *drag_surface; + struct wl_data_source *data_source; +}; + +struct item { + cairo_surface_t *surface; + int seed; + int x, y; +}; + +struct dnd_flower_message { + int seed, x_offset, y_offset; +}; + + +static const int item_width = 64; +static const int item_height = 64; +static const int item_padding = 16; + +static const char flower_mime_type[] = "application/x-wayland-dnd-flower"; +static const char text_mime_type[] = "text/plain;charset=utf-8"; + +static struct item * +item_create(struct display *display, int x, int y, int seed) +{ + struct item *item; + struct timeval tv; + + item = malloc(sizeof *item); + if (item == NULL) + return NULL; + + + gettimeofday(&tv, NULL); + item->seed = seed ? seed : tv.tv_usec; + srandom(item->seed); + + const int petal_count = 3 + random() % 5; + const double r1 = 20 + random() % 10; + const double r2 = 5 + random() % 12; + const double u = (10 + random() % 90) / 100.0; + const double v = (random() % 90) / 100.0; + + cairo_t *cr; + int i; + double t, dt = 2 * M_PI / (petal_count * 2); + double x1, y1, x2, y2, x3, y3; + struct rectangle rect; + + + rect.width = item_width; + rect.height = item_height; + item->surface = + display_create_surface(display, NULL, &rect, SURFACE_SHM); + + item->x = x; + item->y = y; + + cr = cairo_create(item->surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0, 0, 0, 0); + cairo_paint(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_translate(cr, item_width / 2, item_height / 2); + t = random(); + cairo_move_to(cr, cos(t) * r1, sin(t) * r1); + for (i = 0; i < petal_count; i++, t += dt * 2) { + x1 = cos(t) * r1; + y1 = sin(t) * r1; + x2 = cos(t + dt) * r2; + y2 = sin(t + dt) * r2; + x3 = cos(t + 2 * dt) * r1; + y3 = sin(t + 2 * dt) * r1; + + cairo_curve_to(cr, + x1 - y1 * u, y1 + x1 * u, + x2 + y2 * v, y2 - x2 * v, + x2, y2); + + cairo_curve_to(cr, + x2 - y2 * v, y2 + x2 * v, + x3 + y3 * u, y3 - x3 * u, + x3, y3); + } + + cairo_close_path(cr); + + cairo_set_source_rgba(cr, + 0.5 + (random() % 50) / 49.0, + 0.5 + (random() % 50) / 49.0, + 0.5 + (random() % 50) / 49.0, + 0.5 + (random() % 100) / 99.0); + + cairo_fill_preserve(cr); + + cairo_set_line_width(cr, 1); + cairo_set_source_rgba(cr, + 0.5 + (random() % 50) / 49.0, + 0.5 + (random() % 50) / 49.0, + 0.5 + (random() % 50) / 49.0, + 0.5 + (random() % 100) / 99.0); + cairo_stroke(cr); + + cairo_destroy(cr); + + return item; +} + +static void +dnd_redraw_handler(struct widget *widget, void *data) +{ + struct dnd *dnd = data; + struct rectangle allocation; + cairo_t *cr; + cairo_surface_t *surface; + unsigned int i; + + surface = window_get_surface(dnd->window); + cr = cairo_create(surface); + widget_get_allocation(dnd->widget, &allocation); + cairo_rectangle(cr, allocation.x, allocation.y, + allocation.width, allocation.height); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0, 0, 0, 0.8); + cairo_fill(cr); + + cairo_rectangle(cr, allocation.x, allocation.y, + allocation.width, allocation.height); + cairo_clip(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { + if (!dnd->items[i]) + continue; + cairo_set_source_surface(cr, dnd->items[i]->surface, + dnd->items[i]->x + allocation.x, + dnd->items[i]->y + allocation.y); + cairo_paint(cr); + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct dnd *dnd = data; + + window_schedule_redraw(dnd->window); +} + +static int +dnd_add_item(struct dnd *dnd, struct item *item) +{ + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { + if (dnd->items[i] == 0) { + dnd->items[i] = item; + return i; + } + } + return -1; +} + +static struct item * +dnd_get_item(struct dnd *dnd, int32_t x, int32_t y) +{ + struct item *item; + struct rectangle allocation; + unsigned int i; + + widget_get_allocation(dnd->widget, &allocation); + + x -= allocation.x; + y -= allocation.y; + + for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { + item = dnd->items[i]; + if (item && + item->x <= x && x < item->x + item_width && + item->y <= y && y < item->y + item_height) + return item; + } + + return NULL; +} + +static void +data_source_target(void *data, + struct wl_data_source *source, const char *mime_type) +{ + struct dnd_drag *dnd_drag = data; + struct dnd *dnd = dnd_drag->dnd; + cairo_surface_t *surface; + struct wl_buffer *buffer; + + dnd_drag->mime_type = mime_type; + if (mime_type) + surface = dnd_drag->opaque; + else + surface = dnd_drag->translucent; + + buffer = display_get_buffer_for_surface(dnd->display, surface); + wl_surface_attach(dnd_drag->drag_surface, buffer, 0, 0); + wl_surface_damage(dnd_drag->drag_surface, 0, 0, + dnd_drag->width, dnd_drag->height); + wl_surface_commit(dnd_drag->drag_surface); +} + +static void +data_source_send(void *data, struct wl_data_source *source, + const char *mime_type, int32_t fd) +{ + struct dnd_flower_message dnd_flower_message; + struct dnd_drag *dnd_drag = data; + char buffer[128]; + int n; + + if (strcmp(mime_type, flower_mime_type) == 0) { + dnd_flower_message.seed = dnd_drag->item->seed; + dnd_flower_message.x_offset = dnd_drag->x_offset; + dnd_flower_message.y_offset = dnd_drag->y_offset; + + if (write(fd, &dnd_flower_message, + sizeof dnd_flower_message) < 0) + abort(); + } else if (strcmp(mime_type, text_mime_type) == 0) { + n = snprintf(buffer, sizeof buffer, "seed=%d x=%d y=%d\n", + dnd_drag->item->seed, + dnd_drag->x_offset, + dnd_drag->y_offset); + + if (write(fd, buffer, n) < 0) + abort(); + } + + close(fd); +} + +static void +data_source_cancelled(void *data, struct wl_data_source *source) +{ + struct dnd_drag *dnd_drag = data; + + /* The 'cancelled' event means that the source is no longer in + * use by the drag (or current selection). We need to clean + * up the drag object created and the local state. */ + + wl_data_source_destroy(dnd_drag->data_source); + + /* Destroy the item that has been dragged out */ + cairo_surface_destroy(dnd_drag->item->surface); + free(dnd_drag->item); + + wl_surface_destroy(dnd_drag->drag_surface); + + cairo_surface_destroy(dnd_drag->translucent); + cairo_surface_destroy(dnd_drag->opaque); + free(dnd_drag); +} + +static const struct wl_data_source_listener data_source_listener = { + data_source_target, + data_source_send, + data_source_cancelled +}; + +static cairo_surface_t * +create_drag_cursor(struct dnd_drag *dnd_drag, + struct item *item, int32_t x, int32_t y, double opacity) +{ + struct dnd *dnd = dnd_drag->dnd; + cairo_surface_t *surface; + struct wl_cursor_image *pointer; + struct rectangle rectangle; + cairo_pattern_t *pattern; + cairo_t *cr; + + pointer = display_get_pointer_image(dnd->display, CURSOR_DRAGGING); + if (!pointer) { + fprintf(stderr, "WARNING: grabbing cursor image not found\n"); + pointer = display_get_pointer_image(dnd->display, + CURSOR_LEFT_PTR); + assert(pointer && "no cursor image found"); + } + + rectangle.width = item_width + 2 * pointer->width; + rectangle.height = item_height + 2 * pointer->height; + surface = display_create_surface(dnd->display, NULL, &rectangle, + SURFACE_SHM); + + cr = cairo_create(surface); + cairo_translate(cr, pointer->width, pointer->height); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0, 0, 0, 0); + cairo_paint(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cr, item->surface, 0, 0); + pattern = cairo_pattern_create_rgba(0, 0, 0, opacity); + cairo_mask(cr, pattern); + cairo_pattern_destroy(pattern); + + /* FIXME: more cairo-gl brokeness */ + surface_flush_device(surface); + cairo_destroy(cr); + + dnd_drag->hotspot_x = pointer->width + x - item->x; + dnd_drag->hotspot_y = pointer->height + y - item->y; + dnd_drag->width = rectangle.width; + dnd_drag->height = rectangle.height; + + return surface; +} + +static void +dnd_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, enum wl_pointer_button_state state, + void *data) +{ + struct dnd *dnd = data; + int32_t x, y; + struct item *item; + struct rectangle allocation; + struct dnd_drag *dnd_drag; + struct display *display; + struct wl_compositor *compositor; + struct wl_buffer *buffer; + unsigned int i; + uint32_t serial; + cairo_surface_t *icon; + + widget_get_allocation(dnd->widget, &allocation); + input_get_position(input, &x, &y); + item = dnd_get_item(dnd, x, y); + x -= allocation.x; + y -= allocation.y; + + if (item && state == WL_POINTER_BUTTON_STATE_PRESSED) { + dnd_drag = xmalloc(sizeof *dnd_drag); + dnd_drag->dnd = dnd; + dnd_drag->input = input; + dnd_drag->time = time; + dnd_drag->item = item; + dnd_drag->x_offset = x - item->x; + dnd_drag->y_offset = y - item->y; + + for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { + if (item == dnd->items[i]){ + dnd->items[i] = 0; + break; + } + } + + display = window_get_display(dnd->window); + compositor = display_get_compositor(display); + serial = display_get_serial(display); + dnd_drag->drag_surface = + wl_compositor_create_surface(compositor); + + input_ungrab(input); + + if (dnd->self_only) { + dnd_drag->data_source = NULL; + } else { + dnd_drag->data_source = + display_create_data_source(dnd->display); + wl_data_source_add_listener(dnd_drag->data_source, + &data_source_listener, + dnd_drag); + wl_data_source_offer(dnd_drag->data_source, + flower_mime_type); + wl_data_source_offer(dnd_drag->data_source, + text_mime_type); + } + + wl_data_device_start_drag(input_get_data_device(input), + dnd_drag->data_source, + window_get_wl_surface(dnd->window), + dnd_drag->drag_surface, + serial); + + input_set_pointer_image(input, CURSOR_DRAGGING); + + dnd_drag->opaque = + create_drag_cursor(dnd_drag, item, x, y, 1); + dnd_drag->translucent = + create_drag_cursor(dnd_drag, item, x, y, 0.2); + + if (dnd->self_only) + icon = dnd_drag->opaque; + else + icon = dnd_drag->translucent; + + buffer = display_get_buffer_for_surface(dnd->display, icon); + wl_surface_attach(dnd_drag->drag_surface, buffer, + -dnd_drag->hotspot_x, -dnd_drag->hotspot_y); + wl_surface_damage(dnd_drag->drag_surface, 0, 0, + dnd_drag->width, dnd_drag->height); + wl_surface_commit(dnd_drag->drag_surface); + + dnd->current_drag = dnd_drag; + window_schedule_redraw(dnd->window); + } +} + +static int +lookup_cursor(struct dnd *dnd, int x, int y) +{ + struct item *item; + + item = dnd_get_item(dnd, x, y); + if (item) + return CURSOR_HAND1; + else + return CURSOR_LEFT_PTR; +} + +static int +dnd_enter_handler(struct widget *widget, + struct input *input, float x, float y, void *data) +{ + struct dnd *dnd = data; + + dnd->current_drag = NULL; + + return lookup_cursor(dnd, x, y); +} + +static int +dnd_motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + return lookup_cursor(data, x, y); +} + +static void +dnd_data_handler(struct window *window, + struct input *input, + float x, float y, const char **types, void *data) +{ + struct dnd *dnd = data; + int i, has_flower = 0; + + if (!types) + return; + for (i = 0; types[i]; i++) + if (strcmp(types[i], flower_mime_type) == 0) + has_flower = 1; + + if (dnd_get_item(dnd, x, y) || dnd->self_only || !has_flower) { + input_accept(input, NULL); + } else { + input_accept(input, flower_mime_type); + } +} + +static void +dnd_receive_func(void *data, size_t len, int32_t x, int32_t y, void *user_data) +{ + struct dnd *dnd = user_data; + struct dnd_flower_message *message = data; + struct item *item; + struct rectangle allocation; + + if (len == 0) { + return; + } else if (len != sizeof *message) { + fprintf(stderr, "odd message length %zu, expected %zu\n", + len, sizeof *message); + return; + } + + widget_get_allocation(dnd->widget, &allocation); + item = item_create(dnd->display, + x - message->x_offset - allocation.x, + y - message->y_offset - allocation.y, + message->seed); + + dnd_add_item(dnd, item); + window_schedule_redraw(dnd->window); +} + +static void +dnd_drop_handler(struct window *window, struct input *input, + int32_t x, int32_t y, void *data) +{ + struct dnd *dnd = data; + struct dnd_flower_message message; + + if (dnd_get_item(dnd, x, y)) { + fprintf(stderr, "got 'drop', but no target\n"); + return; + } + + if (!dnd->self_only) { + input_receive_drag_data(input, + flower_mime_type, + dnd_receive_func, dnd); + } else if (dnd->current_drag) { + message.seed = dnd->current_drag->item->seed; + message.x_offset = dnd->current_drag->x_offset; + message.y_offset = dnd->current_drag->y_offset; + dnd_receive_func(&message, sizeof message, x, y, dnd); + dnd->current_drag = NULL; + } else { + fprintf(stderr, "ignoring drop from another client\n"); + } +} + +static struct dnd * +dnd_create(struct display *display) +{ + struct dnd *dnd; + int x, y; + int32_t width, height; + unsigned int i; + + dnd = xzalloc(sizeof *dnd); + dnd->window = window_create(display); + dnd->widget = frame_create(dnd->window, dnd); + window_set_title(dnd->window, "Wayland Drag and Drop Demo"); + + dnd->display = display; + dnd->key = 100; + + for (i = 0; i < ARRAY_LENGTH(dnd->items); i++) { + x = (i % 4) * (item_width + item_padding) + item_padding; + y = (i / 4) * (item_height + item_padding) + item_padding; + if ((i ^ (i >> 2)) & 1) + dnd->items[i] = item_create(display, x, y, 0); + else + dnd->items[i] = NULL; + } + + window_set_user_data(dnd->window, dnd); + window_set_keyboard_focus_handler(dnd->window, + keyboard_focus_handler); + window_set_data_handler(dnd->window, dnd_data_handler); + window_set_drop_handler(dnd->window, dnd_drop_handler); + + widget_set_redraw_handler(dnd->widget, dnd_redraw_handler); + widget_set_enter_handler(dnd->widget, dnd_enter_handler); + widget_set_motion_handler(dnd->widget, dnd_motion_handler); + widget_set_button_handler(dnd->widget, dnd_button_handler); + + width = 4 * (item_width + item_padding) + item_padding; + height = 4 * (item_height + item_padding) + item_padding; + + frame_set_child_size(dnd->widget, width, height); + + return dnd; +} + +int +main(int argc, char *argv[]) +{ + struct display *d; + struct dnd *dnd; + int i; + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + dnd = dnd_create(d); + + for (i = 1; i < argc; i++) + if (strcmp("--self-only", argv[i]) == 0) + dnd->self_only = 1; + + display_run(d); + + return 0; +} diff --git a/clients/editor.c b/clients/editor.c new file mode 100644 index 00000000..12650f32 --- /dev/null +++ b/clients/editor.c @@ -0,0 +1,1250 @@ +/* + * Copyright © 2012 Openismus GmbH + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "window.h" +#include "text-client-protocol.h" + +struct text_entry { + struct widget *widget; + struct window *window; + char *text; + int active; + uint32_t cursor; + uint32_t anchor; + struct { + char *text; + int32_t cursor; + char *commit; + PangoAttrList *attr_list; + } preedit; + struct { + PangoAttrList *attr_list; + int32_t cursor; + } preedit_info; + struct { + int32_t cursor; + int32_t anchor; + uint32_t delete_index; + uint32_t delete_length; + bool invalid_delete; + } pending_commit; + struct wl_text_input *text_input; + PangoLayout *layout; + struct { + xkb_mod_mask_t shift_mask; + } keysym; + uint32_t serial; + uint32_t reset_serial; + uint32_t content_purpose; + uint32_t click_to_show; + char *preferred_language; + bool button_pressed; +}; + +struct editor { + struct wl_text_input_manager *text_input_manager; + struct display *display; + struct window *window; + struct widget *widget; + struct text_entry *entry; + struct text_entry *editor; + struct text_entry *active_entry; +}; + +static const char * +utf8_end_char(const char *p) +{ + while ((*p & 0xc0) == 0x80) + p++; + return p; +} + +static const char * +utf8_prev_char(const char *s, const char *p) +{ + for (--p; p >= s; --p) { + if ((*p & 0xc0) != 0x80) + return p; + } + return NULL; +} + +static const char * +utf8_next_char(const char *p) +{ + if (*p != 0) + return utf8_end_char(++p); + return NULL; +} + +static void text_entry_redraw_handler(struct widget *widget, void *data); +static void text_entry_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data); +static int text_entry_motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data); +static void text_entry_insert_at_cursor(struct text_entry *entry, const char *text, + int32_t cursor, int32_t anchor); +static void text_entry_set_preedit(struct text_entry *entry, + const char *preedit_text, + int preedit_cursor); +static void text_entry_delete_text(struct text_entry *entry, + uint32_t index, uint32_t length); +static void text_entry_delete_selected_text(struct text_entry *entry); +static void text_entry_reset_preedit(struct text_entry *entry); +static void text_entry_commit_and_reset(struct text_entry *entry); +static void text_entry_get_cursor_rectangle(struct text_entry *entry, struct rectangle *rectangle); +static void text_entry_update(struct text_entry *entry); + +static void +text_input_commit_string(void *data, + struct wl_text_input *text_input, + uint32_t serial, + const char *text) +{ + struct text_entry *entry = data; + + if ((entry->serial - serial) > (entry->serial - entry->reset_serial)) { + fprintf(stderr, "Ignore commit. Serial: %u, Current: %u, Reset: %u\n", + serial, entry->serial, entry->reset_serial); + return; + } + + if (entry->pending_commit.invalid_delete) { + fprintf(stderr, "Ignore commit. Invalid previous delete_surrounding event.\n"); + memset(&entry->pending_commit, 0, sizeof entry->pending_commit); + return; + } + + text_entry_reset_preedit(entry); + + if (entry->pending_commit.delete_length) { + text_entry_delete_text(entry, + entry->pending_commit.delete_index, + entry->pending_commit.delete_length); + } else { + text_entry_delete_selected_text(entry); + } + + text_entry_insert_at_cursor(entry, text, + entry->pending_commit.cursor, + entry->pending_commit.anchor); + + memset(&entry->pending_commit, 0, sizeof entry->pending_commit); + + widget_schedule_redraw(entry->widget); +} + +static void +clear_pending_preedit(struct text_entry *entry) +{ + memset(&entry->pending_commit, 0, sizeof entry->pending_commit); + + pango_attr_list_unref(entry->preedit_info.attr_list); + + entry->preedit_info.cursor = 0; + entry->preedit_info.attr_list = NULL; + + memset(&entry->preedit_info, 0, sizeof entry->preedit_info); +} + +static void +text_input_preedit_string(void *data, + struct wl_text_input *text_input, + uint32_t serial, + const char *text, + const char *commit) +{ + struct text_entry *entry = data; + + if ((entry->serial - serial) > (entry->serial - entry->reset_serial)) { + fprintf(stderr, "Ignore preedit_string. Serial: %u, Current: %u, Reset: %u\n", + serial, entry->serial, entry->reset_serial); + clear_pending_preedit(entry); + return; + } + + if (entry->pending_commit.invalid_delete) { + fprintf(stderr, "Ignore preedit_string. Invalid previous delete_surrounding event.\n"); + clear_pending_preedit(entry); + return; + } + + if (entry->pending_commit.delete_length) { + text_entry_delete_text(entry, + entry->pending_commit.delete_index, + entry->pending_commit.delete_length); + } else { + text_entry_delete_selected_text(entry); + } + + text_entry_set_preedit(entry, text, entry->preedit_info.cursor); + entry->preedit.commit = strdup(commit); + entry->preedit.attr_list = pango_attr_list_ref(entry->preedit_info.attr_list); + + clear_pending_preedit(entry); + + text_entry_update(entry); + + widget_schedule_redraw(entry->widget); +} + +static void +text_input_delete_surrounding_text(void *data, + struct wl_text_input *text_input, + int32_t index, + uint32_t length) +{ + struct text_entry *entry = data; + uint32_t text_length; + + entry->pending_commit.delete_index = entry->cursor + index; + entry->pending_commit.delete_length = length; + entry->pending_commit.invalid_delete = false; + + text_length = strlen(entry->text); + + if (entry->pending_commit.delete_index > text_length || + length > text_length || + entry->pending_commit.delete_index + length > text_length) { + fprintf(stderr, "delete_surrounding_text: Invalid index: %d," \ + "length %u'; cursor: %u text length: %u\n", index, length, entry->cursor, text_length); + entry->pending_commit.invalid_delete = true; + return; + } +} + +static void +text_input_cursor_position(void *data, + struct wl_text_input *text_input, + int32_t index, + int32_t anchor) +{ + struct text_entry *entry = data; + + entry->pending_commit.cursor = index; + entry->pending_commit.anchor = anchor; +} + +static void +text_input_preedit_styling(void *data, + struct wl_text_input *text_input, + uint32_t index, + uint32_t length, + uint32_t style) +{ + struct text_entry *entry = data; + PangoAttribute *attr1 = NULL; + PangoAttribute *attr2 = NULL; + + if (!entry->preedit_info.attr_list) + entry->preedit_info.attr_list = pango_attr_list_new(); + + switch (style) { + case WL_TEXT_INPUT_PREEDIT_STYLE_DEFAULT: + case WL_TEXT_INPUT_PREEDIT_STYLE_UNDERLINE: + attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); + break; + case WL_TEXT_INPUT_PREEDIT_STYLE_INCORRECT: + attr1 = pango_attr_underline_new(PANGO_UNDERLINE_ERROR); + attr2 = pango_attr_underline_color_new(65535, 0, 0); + break; + case WL_TEXT_INPUT_PREEDIT_STYLE_SELECTION: + attr1 = pango_attr_background_new(0.3 * 65535, 0.3 * 65535, 65535); + attr2 = pango_attr_foreground_new(65535, 65535, 65535); + break; + case WL_TEXT_INPUT_PREEDIT_STYLE_HIGHLIGHT: + case WL_TEXT_INPUT_PREEDIT_STYLE_ACTIVE: + attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); + attr2 = pango_attr_weight_new(PANGO_WEIGHT_BOLD); + break; + case WL_TEXT_INPUT_PREEDIT_STYLE_INACTIVE: + attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); + attr2 = pango_attr_foreground_new(0.3 * 65535, 0.3 * 65535, 0.3 * 65535); + break; + } + + if (attr1) { + attr1->start_index = entry->cursor + index; + attr1->end_index = entry->cursor + index + length; + pango_attr_list_insert(entry->preedit_info.attr_list, attr1); + } + + if (attr2) { + attr2->start_index = entry->cursor + index; + attr2->end_index = entry->cursor + index + length; + pango_attr_list_insert(entry->preedit_info.attr_list, attr2); + } +} + +static void +text_input_preedit_cursor(void *data, + struct wl_text_input *text_input, + int32_t index) +{ + struct text_entry *entry = data; + + entry->preedit_info.cursor = index; +} + +static void +text_input_modifiers_map(void *data, + struct wl_text_input *text_input, + struct wl_array *map) +{ + struct text_entry *entry = data; + + entry->keysym.shift_mask = keysym_modifiers_get_mask(map, "Shift"); +} + +static void +text_input_keysym(void *data, + struct wl_text_input *text_input, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state, + uint32_t modifiers) +{ + struct text_entry *entry = data; + const char *state_label = "release"; + const char *key_label = "Unknown"; + const char *new_char; + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + state_label = "pressed"; + } + + if (key == XKB_KEY_Left || + key == XKB_KEY_Right) { + if (state != WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + if (key == XKB_KEY_Left) + new_char = utf8_prev_char(entry->text, entry->text + entry->cursor); + else + new_char = utf8_next_char(entry->text + entry->cursor); + + if (new_char != NULL) { + entry->cursor = new_char - entry->text; + } + + if (!(modifiers & entry->keysym.shift_mask)) + entry->anchor = entry->cursor; + widget_schedule_redraw(entry->widget); + + return; + } + + if (key == XKB_KEY_BackSpace) { + const char *start, *end; + + if (state != WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + text_entry_commit_and_reset(entry); + + start = utf8_prev_char(entry->text, entry->text + entry->cursor); + if (start == NULL) + return; + + end = utf8_next_char(start); + + text_entry_delete_text(entry, + start - entry->text, + end - start); + + return; + } + + switch (key) { + case XKB_KEY_Tab: + key_label = "Tab"; + break; + case XKB_KEY_KP_Enter: + case XKB_KEY_Return: + key_label = "Enter"; + break; + } + + fprintf(stderr, "%s key was %s.\n", key_label, state_label); +} + +static void +text_input_enter(void *data, + struct wl_text_input *text_input, + struct wl_surface *surface) +{ + struct text_entry *entry = data; + + if (surface != window_get_wl_surface(entry->window)) + return; + + entry->active = 1; + + text_entry_update(entry); + entry->reset_serial = entry->serial; + + widget_schedule_redraw(entry->widget); +} + +static void +text_input_leave(void *data, + struct wl_text_input *text_input) +{ + struct text_entry *entry = data; + + text_entry_commit_and_reset(entry); + + entry->active = 0; + + wl_text_input_hide_input_panel(text_input); + + widget_schedule_redraw(entry->widget); +} + +static void +text_input_input_panel_state(void *data, + struct wl_text_input *text_input, + uint32_t state) +{ +} + +static void +text_input_language(void *data, + struct wl_text_input *text_input, + uint32_t serial, + const char *language) +{ + fprintf(stderr, "input language is %s \n", language); +} + +static void +text_input_text_direction(void *data, + struct wl_text_input *text_input, + uint32_t serial, + uint32_t direction) +{ + struct text_entry *entry = data; + PangoContext *context = pango_layout_get_context(entry->layout); + PangoDirection pango_direction; + + + switch (direction) { + case WL_TEXT_INPUT_TEXT_DIRECTION_LTR: + pango_direction = PANGO_DIRECTION_LTR; + break; + case WL_TEXT_INPUT_TEXT_DIRECTION_RTL: + pango_direction = PANGO_DIRECTION_RTL; + break; + case WL_TEXT_INPUT_TEXT_DIRECTION_AUTO: + default: + pango_direction = PANGO_DIRECTION_NEUTRAL; + } + + pango_context_set_base_dir(context, pango_direction); +} + +static const struct wl_text_input_listener text_input_listener = { + text_input_enter, + text_input_leave, + text_input_modifiers_map, + text_input_input_panel_state, + text_input_preedit_string, + text_input_preedit_styling, + text_input_preedit_cursor, + text_input_commit_string, + text_input_cursor_position, + text_input_delete_surrounding_text, + text_input_keysym, + text_input_language, + text_input_text_direction +}; + +static struct text_entry* +text_entry_create(struct editor *editor, const char *text) +{ + struct text_entry *entry; + + entry = xmalloc(sizeof *entry); + memset(entry, 0, sizeof *entry); + + entry->widget = widget_add_widget(editor->widget, entry); + entry->window = editor->window; + entry->text = strdup(text); + entry->active = 0; + entry->cursor = strlen(text); + entry->anchor = entry->cursor; + entry->text_input = wl_text_input_manager_create_text_input(editor->text_input_manager); + wl_text_input_add_listener(entry->text_input, &text_input_listener, entry); + + widget_set_redraw_handler(entry->widget, text_entry_redraw_handler); + widget_set_button_handler(entry->widget, text_entry_button_handler); + widget_set_motion_handler(entry->widget, text_entry_motion_handler); + + return entry; +} + +static void +text_entry_destroy(struct text_entry *entry) +{ + widget_destroy(entry->widget); + wl_text_input_destroy(entry->text_input); + g_clear_object(&entry->layout); + free(entry->text); + free(entry); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct editor *editor = data; + cairo_surface_t *surface; + struct rectangle allocation; + cairo_t *cr; + + surface = window_get_surface(editor->window); + widget_get_allocation(editor->widget, &allocation); + + cr = cairo_create(surface); + cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); + cairo_clip(cr); + + cairo_translate(cr, allocation.x, allocation.y); + + /* Draw background */ + cairo_push_group(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 1, 1, 1, 1); + cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); + cairo_fill(cr); + + cairo_pop_group_to_source(cr); + cairo_paint(cr); + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static void +text_entry_allocate(struct text_entry *entry, int32_t x, int32_t y, + int32_t width, int32_t height) +{ + widget_set_allocation(entry->widget, x, y, width, height); +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct editor *editor = data; + struct rectangle allocation; + + widget_get_allocation(editor->widget, &allocation); + + text_entry_allocate(editor->entry, + allocation.x + 20, allocation.y + 20, + width - 40, height / 2 - 40); + text_entry_allocate(editor->editor, + allocation.x + 20, allocation.y + height / 2 + 20, + width - 40, height / 2 - 40); +} + +static void +text_entry_activate(struct text_entry *entry, + struct wl_seat *seat) +{ + struct wl_surface *surface = window_get_wl_surface(entry->window); + + if (entry->click_to_show && entry->active) { + wl_text_input_show_input_panel(entry->text_input); + + return; + } + + if (!entry->click_to_show) + wl_text_input_show_input_panel(entry->text_input); + + wl_text_input_activate(entry->text_input, + seat, + surface); +} + +static void +text_entry_deactivate(struct text_entry *entry, + struct wl_seat *seat) +{ + wl_text_input_deactivate(entry->text_input, + seat); +} + +static void +text_entry_update_layout(struct text_entry *entry) +{ + char *text; + PangoAttrList *attr_list; + + assert(entry->cursor <= (strlen(entry->text) + + (entry->preedit.text ? strlen(entry->preedit.text) : 0))); + + if (entry->preedit.text) { + text = malloc(strlen(entry->text) + strlen(entry->preedit.text) + 1); + strncpy(text, entry->text, entry->cursor); + strcpy(text + entry->cursor, entry->preedit.text); + strcpy(text + entry->cursor + strlen(entry->preedit.text), + entry->text + entry->cursor); + } else { + text = strdup(entry->text); + } + + if (entry->cursor != entry->anchor) { + int start_index = MIN(entry->cursor, entry->anchor); + int end_index = MAX(entry->cursor, entry->anchor); + PangoAttribute *attr; + + attr_list = pango_attr_list_copy(entry->preedit.attr_list); + + if (!attr_list) + attr_list = pango_attr_list_new(); + + attr = pango_attr_background_new(0.3 * 65535, 0.3 * 65535, 65535); + attr->start_index = start_index; + attr->end_index = end_index; + pango_attr_list_insert(attr_list, attr); + + attr = pango_attr_foreground_new(65535, 65535, 65535); + attr->start_index = start_index; + attr->end_index = end_index; + pango_attr_list_insert(attr_list, attr); + } else { + attr_list = pango_attr_list_ref(entry->preedit.attr_list); + } + + if (entry->preedit.text && !entry->preedit.attr_list) { + PangoAttribute *attr; + + if (!attr_list) + attr_list = pango_attr_list_new(); + + attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); + attr->start_index = entry->cursor; + attr->end_index = entry->cursor + strlen(entry->preedit.text); + pango_attr_list_insert(attr_list, attr); + } + + if (entry->layout) { + pango_layout_set_text(entry->layout, text, -1); + pango_layout_set_attributes(entry->layout, attr_list); + } + + free(text); + pango_attr_list_unref(attr_list); +} + +static void +text_entry_update(struct text_entry *entry) +{ + struct rectangle cursor_rectangle; + + wl_text_input_set_content_type(entry->text_input, + WL_TEXT_INPUT_CONTENT_HINT_NONE, + entry->content_purpose); + + wl_text_input_set_surrounding_text(entry->text_input, + entry->text, + entry->cursor, + entry->anchor); + + if (entry->preferred_language) + wl_text_input_set_preferred_language(entry->text_input, + entry->preferred_language); + + text_entry_get_cursor_rectangle(entry, &cursor_rectangle); + wl_text_input_set_cursor_rectangle(entry->text_input, cursor_rectangle.x, cursor_rectangle.y, + cursor_rectangle.width, cursor_rectangle.height); + + wl_text_input_commit_state(entry->text_input, ++entry->serial); +} + +static void +text_entry_insert_at_cursor(struct text_entry *entry, const char *text, + int32_t cursor, int32_t anchor) +{ + char *new_text = malloc(strlen(entry->text) + strlen(text) + 1); + + strncpy(new_text, entry->text, entry->cursor); + strcpy(new_text + entry->cursor, text); + strcpy(new_text + entry->cursor + strlen(text), + entry->text + entry->cursor); + + free(entry->text); + entry->text = new_text; + if (anchor >= 0) + entry->anchor = entry->cursor + strlen(text) + anchor; + else + entry->anchor = entry->cursor + 1 + anchor; + + if (cursor >= 0) + entry->cursor += strlen(text) + cursor; + else + entry->cursor += 1 + cursor; + + text_entry_update_layout(entry); + + widget_schedule_redraw(entry->widget); + + text_entry_update(entry); +} + +static void +text_entry_reset_preedit(struct text_entry *entry) +{ + entry->preedit.cursor = 0; + + free(entry->preedit.text); + entry->preedit.text = NULL; + + free(entry->preedit.commit); + entry->preedit.commit = NULL; + + pango_attr_list_unref(entry->preedit.attr_list); + entry->preedit.attr_list = NULL; +} + +static void +text_entry_commit_and_reset(struct text_entry *entry) +{ + char *commit = NULL; + + if (entry->preedit.commit) + commit = strdup(entry->preedit.commit); + + text_entry_reset_preedit(entry); + if (commit) { + text_entry_insert_at_cursor(entry, commit, 0, 0); + free(commit); + } + + wl_text_input_reset(entry->text_input); + text_entry_update(entry); + entry->reset_serial = entry->serial; +} + +static void +text_entry_set_preedit(struct text_entry *entry, + const char *preedit_text, + int preedit_cursor) +{ + text_entry_reset_preedit(entry); + + if (!preedit_text) + return; + + entry->preedit.text = strdup(preedit_text); + entry->preedit.cursor = preedit_cursor; + + text_entry_update_layout(entry); + + widget_schedule_redraw(entry->widget); +} + +static uint32_t +text_entry_try_invoke_preedit_action(struct text_entry *entry, + int32_t x, int32_t y, + uint32_t button, + enum wl_pointer_button_state state) +{ + int index, trailing; + uint32_t cursor; + const char *text; + + if (!entry->preedit.text) + return 0; + + pango_layout_xy_to_index(entry->layout, + x * PANGO_SCALE, y * PANGO_SCALE, + &index, &trailing); + + text = pango_layout_get_text(entry->layout); + cursor = g_utf8_offset_to_pointer(text + index, trailing) - text; + + if (cursor < entry->cursor || + cursor > entry->cursor + strlen(entry->preedit.text)) { + return 0; + } + + if (state == WL_POINTER_BUTTON_STATE_RELEASED) + wl_text_input_invoke_action(entry->text_input, + button, + cursor - entry->cursor); + + return 1; +} + +static bool +text_entry_has_preedit(struct text_entry *entry) +{ + return entry->preedit.text && (strlen(entry->preedit.text) > 0); +} + +static void +text_entry_set_cursor_position(struct text_entry *entry, + int32_t x, int32_t y, + bool move_anchor) +{ + int index, trailing; + const char *text; + uint32_t cursor; + + pango_layout_xy_to_index(entry->layout, + x * PANGO_SCALE, y * PANGO_SCALE, + &index, &trailing); + + text = pango_layout_get_text(entry->layout); + + cursor = g_utf8_offset_to_pointer(text + index, trailing) - text; + + if (move_anchor) + entry->anchor = cursor; + + if (text_entry_has_preedit(entry)) { + text_entry_commit_and_reset(entry); + + assert(!text_entry_has_preedit(entry)); + } + + if (entry->cursor == cursor) + return; + + entry->cursor = cursor; + + text_entry_update_layout(entry); + + widget_schedule_redraw(entry->widget); + + text_entry_update(entry); +} + +static void +text_entry_delete_text(struct text_entry *entry, + uint32_t index, uint32_t length) +{ + uint32_t l; + + assert(index <= strlen(entry->text)); + assert(index + length <= strlen(entry->text)); + assert(index + length >= length); + + l = strlen(entry->text + index + length); + memmove(entry->text + index, + entry->text + index + length, + l + 1); + + if (entry->cursor > (index + length)) + entry->cursor -= length; + else if (entry->cursor > index) + entry->cursor = index; + + entry->anchor = entry->cursor; + + text_entry_update_layout(entry); + + widget_schedule_redraw(entry->widget); + + text_entry_update(entry); +} + +static void +text_entry_delete_selected_text(struct text_entry *entry) +{ + uint32_t start_index = entry->anchor < entry->cursor ? entry->anchor : entry->cursor; + uint32_t end_index = entry->anchor < entry->cursor ? entry->cursor : entry->anchor; + + if (entry->anchor == entry->cursor) + return; + + text_entry_delete_text(entry, start_index, end_index - start_index); + + entry->anchor = entry->cursor; +} + +static void +text_entry_get_cursor_rectangle(struct text_entry *entry, struct rectangle *rectangle) +{ + struct rectangle allocation; + PangoRectangle extents; + PangoRectangle cursor_pos; + + widget_get_allocation(entry->widget, &allocation); + + if (entry->preedit.text && entry->preedit.cursor < 0) { + rectangle->x = 0; + rectangle->y = 0; + rectangle->width = 0; + rectangle->height = 0; + return; + } + + + pango_layout_get_extents(entry->layout, &extents, NULL); + pango_layout_get_cursor_pos(entry->layout, + entry->cursor + entry->preedit.cursor, + &cursor_pos, NULL); + + rectangle->x = allocation.x + (allocation.height / 2) + PANGO_PIXELS(cursor_pos.x); + rectangle->y = allocation.y + 10 + PANGO_PIXELS(cursor_pos.y); + rectangle->width = PANGO_PIXELS(cursor_pos.width); + rectangle->height = PANGO_PIXELS(cursor_pos.height); +} + +static void +text_entry_draw_cursor(struct text_entry *entry, cairo_t *cr) +{ + PangoRectangle extents; + PangoRectangle cursor_pos; + + if (entry->preedit.text && entry->preedit.cursor < 0) + return; + + pango_layout_get_extents(entry->layout, &extents, NULL); + pango_layout_get_cursor_pos(entry->layout, + entry->cursor + entry->preedit.cursor, + &cursor_pos, NULL); + + cairo_set_line_width(cr, 1.0); + cairo_move_to(cr, PANGO_PIXELS(cursor_pos.x), PANGO_PIXELS(cursor_pos.y)); + cairo_line_to(cr, PANGO_PIXELS(cursor_pos.x), PANGO_PIXELS(cursor_pos.y) + PANGO_PIXELS(cursor_pos.height)); + cairo_stroke(cr); +} + +static const int text_offset_left = 10; + +static void +text_entry_redraw_handler(struct widget *widget, void *data) +{ + struct text_entry *entry = data; + cairo_surface_t *surface; + struct rectangle allocation; + cairo_t *cr; + + surface = window_get_surface(entry->window); + widget_get_allocation(entry->widget, &allocation); + + cr = cairo_create(surface); + cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); + cairo_clip(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + + cairo_push_group(cr); + cairo_translate(cr, allocation.x, allocation.y); + + cairo_set_source_rgba(cr, 1, 1, 1, 1); + cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); + cairo_fill(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + if (entry->active) { + cairo_rectangle(cr, 0, 0, allocation.width, allocation.height); + cairo_set_line_width (cr, 3); + cairo_set_source_rgba(cr, 0, 0, 1, 1.0); + cairo_stroke(cr); + } + + cairo_set_source_rgba(cr, 0, 0, 0, 1); + + cairo_translate(cr, text_offset_left, allocation.height / 2); + + if (!entry->layout) + entry->layout = pango_cairo_create_layout(cr); + else + pango_cairo_update_layout(cr, entry->layout); + + text_entry_update_layout(entry); + + pango_cairo_show_layout(cr, entry->layout); + + text_entry_draw_cursor(entry, cr); + + cairo_pop_group_to_source(cr); + cairo_paint(cr); + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static int +text_entry_motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct text_entry *entry = data; + struct rectangle allocation; + + if (!entry->button_pressed) { + return CURSOR_IBEAM; + } + + widget_get_allocation(entry->widget, &allocation); + + text_entry_set_cursor_position(entry, + x - allocation.x - text_offset_left, + y - allocation.y - text_offset_left, + false); + + return CURSOR_IBEAM; +} + +static void +text_entry_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct text_entry *entry = data; + struct rectangle allocation; + struct editor *editor; + int32_t x, y; + uint32_t result; + + widget_get_allocation(entry->widget, &allocation); + input_get_position(input, &x, &y); + + x -= allocation.x + text_offset_left; + y -= allocation.y + text_offset_left; + + editor = window_get_user_data(entry->window); + + if (button == BTN_LEFT) { + entry->button_pressed = (state == WL_POINTER_BUTTON_STATE_PRESSED); + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + input_grab(input, entry->widget, button); + else + input_ungrab(input); + } + + if (text_entry_has_preedit(entry)) { + result = text_entry_try_invoke_preedit_action(entry, x, y, button, state); + + if (result) + return; + } + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + struct wl_seat *seat = input_get_seat(input); + + text_entry_activate(entry, seat); + editor->active_entry = entry; + + text_entry_set_cursor_position(entry, x, y, true); + } +} + +static void +editor_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct editor *editor = data; + + if (button != BTN_LEFT) { + return; + } + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + struct wl_seat *seat = input_get_seat(input); + + text_entry_deactivate(editor->entry, seat); + text_entry_deactivate(editor->editor, seat); + editor->active_entry = NULL; + } +} + +static void +key_handler(struct window *window, + struct input *input, uint32_t time, + uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, + void *data) +{ + struct editor *editor = data; + struct text_entry *entry; + const char *new_char; + char text[16]; + + if (!editor->active_entry) + return; + + entry = editor->active_entry; + + if (state != WL_KEYBOARD_KEY_STATE_PRESSED) + return; + + switch (sym) { + case XKB_KEY_BackSpace: + text_entry_commit_and_reset(entry); + + new_char = utf8_prev_char(entry->text, entry->text + entry->cursor); + if (new_char != NULL) + text_entry_delete_text(entry, + new_char - entry->text, + (entry->text + entry->cursor) - new_char); + break; + case XKB_KEY_Delete: + text_entry_commit_and_reset(entry); + + new_char = utf8_next_char(entry->text + entry->cursor); + if (new_char != NULL) + text_entry_delete_text(entry, + entry->cursor, + new_char - (entry->text + entry->cursor)); + break; + case XKB_KEY_Left: + text_entry_commit_and_reset(entry); + + new_char = utf8_prev_char(entry->text, entry->text + entry->cursor); + if (new_char != NULL) { + entry->cursor = new_char - entry->text; + if (!(input_get_modifiers(input) & MOD_SHIFT_MASK)) + entry->anchor = entry->cursor; + widget_schedule_redraw(entry->widget); + } + break; + case XKB_KEY_Right: + text_entry_commit_and_reset(entry); + + new_char = utf8_next_char(entry->text + entry->cursor); + if (new_char != NULL) { + entry->cursor = new_char - entry->text; + if (!(input_get_modifiers(input) & MOD_SHIFT_MASK)) + entry->anchor = entry->cursor; + widget_schedule_redraw(entry->widget); + } + break; + case XKB_KEY_Escape: + break; + default: + if (xkb_keysym_to_utf8(sym, text, sizeof(text)) <= 0) + break; + + text_entry_commit_and_reset(entry); + + text_entry_insert_at_cursor(entry, text, 0, 0); + break; + } + + widget_schedule_redraw(entry->widget); +} + +static void +global_handler(struct display *display, uint32_t name, + const char *interface, uint32_t version, void *data) +{ + struct editor *editor = data; + + if (!strcmp(interface, "wl_text_input_manager")) { + editor->text_input_manager = + display_bind(display, name, + &wl_text_input_manager_interface, 1); + } +} + +int +main(int argc, char *argv[]) +{ + struct editor editor; + int i; + uint32_t click_to_show = 0; + const char *preferred_language = NULL; + + for (i = 1; i < argc; i++) { + if (strcmp("--click-to-show", argv[i]) == 0) + click_to_show = 1; + else if (strcmp("--preferred-language", argv[i]) == 0) { + if (i + 1 < argc) { + preferred_language = argv[i + 1]; + i++; + } + } + } + + memset(&editor, 0, sizeof editor); + +#ifdef HAVE_PANGO + g_type_init(); +#endif + + editor.display = display_create(&argc, argv); + if (editor.display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + display_set_user_data(editor.display, &editor); + display_set_global_handler(editor.display, global_handler); + + editor.window = window_create(editor.display); + editor.widget = frame_create(editor.window, &editor); + + editor.entry = text_entry_create(&editor, "Entry"); + editor.entry->click_to_show = click_to_show; + if (preferred_language) + editor.entry->preferred_language = strdup(preferred_language); + editor.editor = text_entry_create(&editor, "Numeric"); + editor.editor->content_purpose = WL_TEXT_INPUT_CONTENT_PURPOSE_NUMBER; + editor.editor->click_to_show = click_to_show; + + window_set_title(editor.window, "Text Editor"); + window_set_key_handler(editor.window, key_handler); + window_set_user_data(editor.window, &editor); + + widget_set_redraw_handler(editor.widget, redraw_handler); + widget_set_resize_handler(editor.widget, resize_handler); + widget_set_button_handler(editor.widget, editor_button_handler); + + window_schedule_resize(editor.window, 500, 400); + + display_run(editor.display); + + text_entry_destroy(editor.entry); + text_entry_destroy(editor.editor); + + return 0; +} diff --git a/clients/eventdemo.c b/clients/eventdemo.c new file mode 100644 index 00000000..05ad5dcb --- /dev/null +++ b/clients/eventdemo.c @@ -0,0 +1,417 @@ +/* + * Copyright © 2011 Tim Wiederhake + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +/** + * \file eventdemo.c + * \brief Demonstrate the use of Wayland's toytoolkit. + * + * Heavily commented demo program that can report all events that are + * dispatched to the window. For other functionality, eg. opengl/egl, + * drag and drop, etc. have a look at the other demos. + * \author Tim Wiederhake + */ + +#include +#include + +#include + +#include "window.h" + +/** window title */ +static char *title = "EventDemo"; + +/** window width */ +static int width = 500; + +/** window height */ +static int height = 400; + +/** set if window has no borders */ +static int noborder = 0; + +/** if non-zero, maximum window width */ +static int width_max = 0; + +/** if non-zero, maximum window height */ +static int height_max = 0; + +/** set to log redrawing */ +static int log_redraw = 0; + +/** set to log resizing */ +static int log_resize = 0; + +/** set to log keyboard focus */ +static int log_focus = 0; + +/** set to log key events */ +static int log_key = 0; + +/** set to log button events */ +static int log_button = 0; + +/** set to log axis events */ +static int log_axis = 0; + +/** set to log motion events */ +static int log_motion = 0; + +/** + * \struct eventdemo + * \brief Holds all data the program needs per window + * + * In this demo the struct holds the position of a + * red rectangle that is drawn in the window's area. + */ +struct eventdemo { + struct window *window; + struct widget *widget; + struct display *display; + + int x, y, w, h; +}; + +/** + * \brief CALLBACK function, Wayland requests the window to redraw. + * \param widget widget to be redrawn + * \param data user data associated to the window + * + * Draws a red rectangle as demonstration of per-window data. + */ +static void +redraw_handler(struct widget *widget, void *data) +{ + struct eventdemo *e = data; + cairo_surface_t *surface; + cairo_t *cr; + struct rectangle rect; + + if (log_redraw) + printf("redraw\n"); + + widget_get_allocation(e->widget, &rect); + surface = window_get_surface(e->window); + + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + + cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height); + cairo_set_source_rgba(cr, 0, 0, 0, 0.8); + cairo_fill(cr); + + cairo_rectangle(cr, e->x, e->y, e->w, e->h); + cairo_set_source_rgba(cr, 1.0, 0, 0, 1); + cairo_fill(cr); + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +/** + * \brief CALLBACK function, Wayland requests the window to resize. + * \param widget widget to be resized + * \param width desired width + * \param height desired height + * \param data user data associated to the window + */ + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct eventdemo *e = data; + if (log_resize) + printf("resize width: %d, height: %d\n", width, height); + + /* if a maximum width is set, constrain to it */ + if (width_max && width_max < width) + width = width_max; + + /* if a maximum height is set, constrain to it */ + if (height_max && height_max < height) + height = height_max; + + /* set the new window dimensions */ + widget_set_size(e->widget, width, height); +} + +/** + * \brief CALLBACK function, Wayland informs about keyboard focus change + * \param window window + * \param device device that caused the focus change + * \param data user data associated to the window + */ +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + int32_t x, y; + struct eventdemo *e = data; + + if(log_focus) { + if(device) { + input_get_position(device, &x, &y); + printf("focus x: %d, y: %d\n", x, y); + } else { + printf("focus lost\n"); + } + } + + window_schedule_redraw(e->window); +} + +/** + * \brief CALLBACK function, Wayland informs about key event + * \param window window + * \param key keycode + * \param unicode associated character + * \param state pressed or released + * \param modifiers modifiers: ctrl, alt, meta etc. + * \param data user data associated to the window + */ +static void +key_handler(struct window *window, struct input *input, uint32_t time, + uint32_t key, uint32_t unicode, enum wl_keyboard_key_state state, + void *data) +{ + uint32_t modifiers = input_get_modifiers(input); + + if(!log_key) + return; + + printf("key key: %d, unicode: %d, state: %s, modifiers: 0x%x\n", + key, unicode, + (state == WL_KEYBOARD_KEY_STATE_PRESSED) ? "pressed" : + "released", + modifiers); +} + +/** + * \brief CALLBACK function, Wayland informs about button event + * \param widget widget + * \param input input device that caused the button event + * \param time time the event happened + * \param button button + * \param state pressed or released + * \param data user data associated to the window + */ +static void +button_handler(struct widget *widget, struct input *input, uint32_t time, + uint32_t button, enum wl_pointer_button_state state, void *data) +{ + int32_t x, y; + + if (!log_button) + return; + + input_get_position(input, &x, &y); + printf("button time: %d, button: %d, state: %s, x: %d, y: %d\n", + time, button, + (state == WL_POINTER_BUTTON_STATE_PRESSED) ? "pressed" : + "released", + x, y); +} + +/** + * \brief CALLBACK function, Wayland informs about axis event + * \param widget widget + * \param input input device that caused the axis event + * \param time time the event happened + * \param axis vertical or horizontal + * \param value amount of scrolling + * \param data user data associated to the widget + */ +static void +axis_handler(struct widget *widget, struct input *input, uint32_t time, + uint32_t axis, wl_fixed_t value, void *data) +{ + if (!log_axis) + return; + + printf("axis time: %d, axis: %s, value: %f\n", + time, + axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? "vertical" : + "horizontal", + wl_fixed_to_double(value)); +} + +/** + * \brief CALLBACK function, Waylands informs about pointer motion + * \param widget widget + * \param input input device that caused the motion event + * \param time time the event happened + * \param x absolute x position + * \param y absolute y position + * \param sx x position relative to the window + * \param sy y position relative to the window + * \param data user data associated to the window + * + * Demonstrates the use of different cursors + */ +static int +motion_handler(struct widget *widget, struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct eventdemo *e = data; + + if (log_motion) { + printf("motion time: %d, x: %f, y: %f\n", time, x, y); + } + + if (x > e->x && x < e->x + e->w) + if (y > e->y && y < e->y + e->h) + return CURSOR_HAND1; + + return CURSOR_LEFT_PTR; +} + +/** + * \brief Create and initialise a new eventdemo window. + * The returned eventdemo instance should be destroyed using \c eventdemo_destroy(). + * \param d associated display + */ +static struct eventdemo * +eventdemo_create(struct display *d) +{ + struct eventdemo *e; + + e = malloc(sizeof (struct eventdemo)); + if(e == NULL) + return NULL; + + e->window = window_create(d); + + if (noborder) { + /* Demonstrate how to create a borderless window. + * Move windows with META + left mouse button. + */ + e->widget = window_add_widget(e->window, e); + } else { + e->widget = frame_create(e->window, e); + window_set_title(e->window, title); + } + e->display = d; + + /* The eventdemo window draws a red rectangle as a demonstration + * of per-window data. The dimensions of that rectangle are set + * here. + */ + e->x = width * 1.0 / 4.0; + e->w = width * 2.0 / 4.0; + e->y = height * 1.0 / 4.0; + e->h = height * 2.0 / 4.0; + + /* Connect the user data to the window */ + window_set_user_data(e->window, e); + + /* Set the callback redraw handler for the window */ + widget_set_redraw_handler(e->widget, redraw_handler); + + /* Set the callback resize handler for the window */ + widget_set_resize_handler(e->widget, resize_handler); + + /* Set the callback focus handler for the window */ + window_set_keyboard_focus_handler(e->window, + keyboard_focus_handler); + + /* Set the callback key handler for the window */ + window_set_key_handler(e->window, key_handler); + + /* Set the callback button handler for the window */ + widget_set_button_handler(e->widget, button_handler); + + /* Set the callback motion handler for the window */ + widget_set_motion_handler(e->widget, motion_handler); + + /* Set the callback axis handler for the window */ + widget_set_axis_handler(e->widget, axis_handler); + + /* Initial drawing of the window */ + window_schedule_resize(e->window, width, height); + + return e; +} +/** + * \brief Destroy eventdemo instance previously created by \c eventdemo_create(). + * \param eventdemo eventdemo instance to destroy + */ +static void eventdemo_destroy(struct eventdemo * eventdemo) +{ + widget_destroy(eventdemo->widget); + window_destroy(eventdemo->window); + free(eventdemo); +} +/** + * \brief command line options for eventdemo + */ +static const struct weston_option eventdemo_options[] = { + { WESTON_OPTION_STRING, "title", 0, &title }, + { WESTON_OPTION_INTEGER, "width", 'w', &width }, + { WESTON_OPTION_INTEGER, "height", 'h', &height }, + { WESTON_OPTION_INTEGER, "max-width", 0, &width_max }, + { WESTON_OPTION_INTEGER, "max-height", 0, &height_max }, + { WESTON_OPTION_BOOLEAN, "no-border", 'b', &noborder }, + { WESTON_OPTION_BOOLEAN, "log-redraw", '0', &log_redraw }, + { WESTON_OPTION_BOOLEAN, "log-resize", '0', &log_resize }, + { WESTON_OPTION_BOOLEAN, "log-focus", '0', &log_focus }, + { WESTON_OPTION_BOOLEAN, "log-key", '0', &log_key }, + { WESTON_OPTION_BOOLEAN, "log-button", '0', &log_button }, + { WESTON_OPTION_BOOLEAN, "log-axis", '0', &log_axis }, + { WESTON_OPTION_BOOLEAN, "log-motion", '0', &log_motion }, +}; + +/** + * \brief Connects to the display, creates the window and hands over + * to the main loop. + */ +int +main(int argc, char *argv[]) +{ + struct display *d; + struct eventdemo *e; + + parse_options(eventdemo_options, + ARRAY_LENGTH(eventdemo_options), &argc, argv); + + /* Connect to the display and have the arguments parsed */ + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + /* Create new eventdemo window */ + e = eventdemo_create(d); + if (e == NULL) { + fprintf(stderr, "failed to create eventdemo: %m\n"); + return -1; + } + + display_run(d); + + /* Release resources */ + eventdemo_destroy(e); + display_destroy(d); + + return 0; +} diff --git a/clients/flower.c b/clients/flower.c new file mode 100644 index 00000000..825c833e --- /dev/null +++ b/clients/flower.c @@ -0,0 +1,198 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "window.h" + +struct flower { + struct display *display; + struct window *window; + struct widget *widget; + int width, height; +}; + +static void +set_random_color(cairo_t *cr) +{ + cairo_set_source_rgba(cr, + 0.5 + (random() % 50) / 49.0, + 0.5 + (random() % 50) / 49.0, + 0.5 + (random() % 50) / 49.0, + 0.5 + (random() % 100) / 99.0); +} + + +static void +draw_stuff(cairo_surface_t *surface, int width, int height) +{ + const int petal_count = 3 + random() % 5; + const double r1 = 60 + random() % 35; + const double r2 = 20 + random() % 40; + const double u = (10 + random() % 90) / 100.0; + const double v = (random() % 90) / 100.0; + + cairo_t *cr; + int i; + double t, dt = 2 * M_PI / (petal_count * 2); + double x1, y1, x2, y2, x3, y3; + + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0, 0, 0, 0); + cairo_paint(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_translate(cr, width / 2, height / 2); + cairo_move_to(cr, cos(0) * r1, sin(0) * r1); + for (t = 0, i = 0; i < petal_count; i++, t += dt * 2) { + x1 = cos(t) * r1; + y1 = sin(t) * r1; + x2 = cos(t + dt) * r2; + y2 = sin(t + dt) * r2; + x3 = cos(t + 2 * dt) * r1; + y3 = sin(t + 2 * dt) * r1; + + cairo_curve_to(cr, + x1 - y1 * u, y1 + x1 * u, + x2 + y2 * v, y2 - x2 * v, + x2, y2); + + cairo_curve_to(cr, + x2 - y2 * v, y2 + x2 * v, + x3 + y3 * u, y3 - x3 * u, + x3, y3); + } + + cairo_close_path(cr); + set_random_color(cr); + cairo_fill_preserve(cr); + set_random_color(cr); + cairo_stroke(cr); + + cairo_destroy(cr); +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct flower *flower = data; + + /* Dont resize me */ + widget_set_size(flower->widget, flower->width, flower->height); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct flower *flower = data; + cairo_surface_t *surface; + + surface = window_get_surface(flower->window); + if (surface == NULL || + cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "failed to create cairo egl surface\n"); + return; + } + + draw_stuff(surface, flower->width, flower->height); + cairo_surface_destroy(surface); +} + +static void +button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, enum wl_pointer_button_state state, void *data) +{ + struct flower *flower = data; + + switch (button) { + case BTN_LEFT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + window_move(flower->window, input, + display_get_serial(flower->display)); + break; + case BTN_MIDDLE: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + widget_schedule_redraw(widget); + break; + case BTN_RIGHT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + window_show_frame_menu(flower->window, input, time); + break; + } +} + +static void +touch_down_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float x, float y, void *data) +{ + struct flower *flower = data; + window_touch_move(flower->window, input, + display_get_serial(flower->display)); +} + +int main(int argc, char *argv[]) +{ + struct flower flower; + struct display *d; + struct timeval tv; + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + gettimeofday(&tv, NULL); + srandom(tv.tv_usec); + + flower.width = 200; + flower.height = 200; + flower.display = d; + flower.window = window_create(d); + flower.widget = window_add_widget(flower.window, &flower); + window_set_title(flower.window, "Flower"); + + widget_set_resize_handler(flower.widget, resize_handler); + widget_set_redraw_handler(flower.widget, redraw_handler); + widget_set_button_handler(flower.widget, button_handler); + widget_set_default_cursor(flower.widget, CURSOR_HAND1); + widget_set_touch_down_handler(flower.widget, touch_down_handler); + + window_schedule_resize(flower.window, flower.width, flower.height); + + display_run(d); + + return 0; +} diff --git a/clients/fullscreen.c b/clients/fullscreen.c new file mode 100644 index 00000000..72e2c81f --- /dev/null +++ b/clients/fullscreen.c @@ -0,0 +1,368 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "window.h" + +struct fullscreen { + struct display *display; + struct window *window; + struct widget *widget; + int width, height; + int fullscreen; + float pointer_x, pointer_y; + enum wl_shell_surface_fullscreen_method fullscreen_method; +}; + +static void +fullscreen_handler(struct window *window, void *data) +{ + struct fullscreen *fullscreen = data; + + fullscreen->fullscreen ^= 1; + window_set_fullscreen(window, fullscreen->fullscreen); +} + +static void +resize_handler(struct widget *widget, int width, int height, void *data) +{ + struct fullscreen *fullscreen = data; + + widget_set_size(widget, fullscreen->width, fullscreen->height); +} + +static void +draw_string(cairo_t *cr, + const char *fmt, ...) +{ + char buffer[4096]; + char *p, *end; + va_list argp; + cairo_text_extents_t text_extents; + cairo_font_extents_t font_extents; + + cairo_save(cr); + + cairo_select_font_face(cr, "sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, 14); + + cairo_font_extents (cr, &font_extents); + + va_start(argp, fmt); + + vsnprintf(buffer, sizeof(buffer), fmt, argp); + + p = buffer; + while (*p) { + end = strchr(p, '\n'); + if (end) + *end = 0; + + cairo_show_text(cr, p); + cairo_text_extents (cr, p, &text_extents); + cairo_rel_move_to (cr, -text_extents.x_advance, font_extents.height); + + if (end) + p = end + 1; + else + break; + } + + va_end(argp); + + cairo_restore(cr); + +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct fullscreen *fullscreen = data; + struct rectangle allocation; + cairo_surface_t *surface; + cairo_t *cr; + int i; + double x, y, border; + const char *method_name[] = { "default", "scale", "driver", "fill" }; + + surface = window_get_surface(fullscreen->window); + if (surface == NULL || + cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "failed to create cairo egl surface\n"); + return; + } + + widget_get_allocation(fullscreen->widget, &allocation); + + cr = widget_cairo_create(widget); + + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_paint (cr); + + cairo_set_source_rgb(cr, 0, 0, 1); + cairo_set_line_width (cr, 10); + cairo_rectangle(cr, 5, 5, allocation.width - 10, allocation.height - 10); + cairo_stroke (cr); + + cairo_move_to(cr, + allocation.x + 15, + allocation.y + 25); + cairo_set_source_rgb(cr, 1, 1, 1); + + draw_string(cr, + "Surface size: %d, %d\n" + "Scale: %d, transform: %d\n" + "Pointer: %f,%f\n" + "Fullscreen: %d, method: %s\n" + "Keys: (s)cale, (t)ransform, si(z)e, (m)ethod, (f)ullscreen, (q)uit\n", + fullscreen->width, fullscreen->height, + window_get_buffer_scale (fullscreen->window), + window_get_buffer_transform (fullscreen->window), + fullscreen->pointer_x, fullscreen->pointer_y, + fullscreen->fullscreen, method_name[fullscreen->fullscreen_method]); + + y = 100; + i = 0; + while (y + 60 < fullscreen->height) { + border = (i++ % 2 == 0) ? 1 : 0.5; + + x = 50; + cairo_set_line_width (cr, border); + while (x + 70 < fullscreen->width) { + if (fullscreen->pointer_x >= x && fullscreen->pointer_x < x + 50 && + fullscreen->pointer_y >= y && fullscreen->pointer_y < y + 40) { + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_rectangle(cr, + x, y, + 50, 40); + cairo_fill(cr); + } + cairo_set_source_rgb(cr, 0, 1, 0); + cairo_rectangle(cr, + x + border/2.0, y + border/2.0, + 50, 40); + cairo_stroke(cr); + x += 60; + } + + y += 50; + } + + cairo_destroy(cr); +} + +static void +key_handler(struct window *window, struct input *input, uint32_t time, + uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, + void *data) +{ + struct fullscreen *fullscreen = data; + int transform, scale; + static int current_size = 0; + int widths[] = { 640, 320, 800, 400 }; + int heights[] = { 480, 240, 600, 300 }; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + switch (sym) { + case XKB_KEY_t: + transform = window_get_buffer_transform (window); + transform = (transform + 1) % 8; + window_set_buffer_transform(window, transform); + window_schedule_redraw(window); + break; + + case XKB_KEY_s: + scale = window_get_buffer_scale (window); + if (scale == 1) + scale = 2; + else + scale = 1; + window_set_buffer_scale(window, scale); + window_schedule_redraw(window); + break; + + case XKB_KEY_z: + current_size = (current_size + 1) % 4; + fullscreen->width = widths[current_size]; + fullscreen->height = heights[current_size]; + window_schedule_resize(fullscreen->window, + fullscreen->width, fullscreen->height); + break; + + case XKB_KEY_m: + fullscreen->fullscreen_method = (fullscreen->fullscreen_method + 1) % 4; + window_set_fullscreen_method(fullscreen->window, + fullscreen->fullscreen_method); + window_schedule_redraw(window); + break; + + case XKB_KEY_f: + fullscreen->fullscreen ^= 1; + window_set_fullscreen(window, fullscreen->fullscreen); + break; + + case XKB_KEY_q: + exit (0); + break; + } +} + +static int +motion_handler(struct widget *widget, + struct input *input, + uint32_t time, + float x, + float y, void *data) +{ + struct fullscreen *fullscreen = data; + + fullscreen->pointer_x = x; + fullscreen->pointer_y = y; + + widget_schedule_redraw(widget); + return 0; +} + + +static void +button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, enum wl_pointer_button_state state, void *data) +{ + struct fullscreen *fullscreen = data; + + switch (button) { + case BTN_LEFT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + window_move(fullscreen->window, input, + display_get_serial(fullscreen->display)); + break; + case BTN_RIGHT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + window_show_frame_menu(fullscreen->window, input, time); + break; + } +} + +static void +touch_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float x, float y, void *data) +{ + struct fullscreen *fullscreen = data; + window_touch_move(fullscreen->window, input, + display_get_serial(fullscreen->display)); +} + +static void +usage(int error_code) +{ + fprintf(stderr, "Usage: fullscreen [OPTIONS]\n\n" + " -w \tSet window width to \n" + " -h \tSet window height to \n" + " --help\tShow this help text\n\n"); + + exit(error_code); +} + +int main(int argc, char *argv[]) +{ + struct fullscreen fullscreen; + struct display *d; + int i; + + fullscreen.width = 640; + fullscreen.height = 480; + fullscreen.fullscreen = 0; + fullscreen.fullscreen_method = + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-w") == 0) { + if (++i >= argc) + usage(EXIT_FAILURE); + + fullscreen.width = atol(argv[i]); + } else if (strcmp(argv[i], "-h") == 0) { + if (++i >= argc) + usage(EXIT_FAILURE); + + fullscreen.height = atol(argv[i]); + } else if (strcmp(argv[i], "--help") == 0) + usage(EXIT_SUCCESS); + else + usage(EXIT_FAILURE); + } + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + fullscreen.display = d; + fullscreen.window = window_create(d); + fullscreen.widget = + window_add_widget(fullscreen.window, &fullscreen); + + window_set_title(fullscreen.window, "Fullscreen"); + window_set_fullscreen_method(fullscreen.window, + fullscreen.fullscreen_method); + + widget_set_transparent(fullscreen.widget, 0); + widget_set_default_cursor(fullscreen.widget, CURSOR_LEFT_PTR); + + widget_set_resize_handler(fullscreen.widget, resize_handler); + widget_set_redraw_handler(fullscreen.widget, redraw_handler); + widget_set_button_handler(fullscreen.widget, button_handler); + widget_set_motion_handler(fullscreen.widget, motion_handler); + + widget_set_touch_down_handler(fullscreen.widget, touch_handler); + + window_set_key_handler(fullscreen.window, key_handler); + window_set_fullscreen_handler(fullscreen.window, fullscreen_handler); + + window_set_user_data(fullscreen.window, &fullscreen); + /* Hack to set minimum allocation so we can shrink later */ + window_schedule_resize(fullscreen.window, + 1, 1); + window_schedule_resize(fullscreen.window, + fullscreen.width, fullscreen.height); + + display_run(d); + + return 0; +} diff --git a/clients/gears.c b/clients/gears.c new file mode 100644 index 00000000..30f4e688 --- /dev/null +++ b/clients/gears.c @@ -0,0 +1,485 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "window.h" + +struct gears { + struct window *window; + struct widget *widget; + + struct display *d; + + EGLDisplay display; + EGLDisplay config; + EGLContext context; + GLfloat angle; + + struct { + GLfloat rotx; + GLfloat roty; + } view; + + int button_down; + int last_x, last_y; + + GLint gear_list[3]; + int fullscreen; + int frames; + uint32_t last_fps; +}; + +struct gear_template { + GLfloat material[4]; + GLfloat inner_radius; + GLfloat outer_radius; + GLfloat width; + GLint teeth; + GLfloat tooth_depth; +}; + +static const struct gear_template gear_templates[] = { + { { 0.8, 0.1, 0.0, 1.0 }, 1.0, 4.0, 1.0, 20, 0.7 }, + { { 0.0, 0.8, 0.2, 1.0 }, 0.5, 2.0, 2.0, 10, 0.7 }, + { { 0.2, 0.2, 1.0, 1.0 }, 1.3, 2.0, 0.5, 10, 0.7 }, +}; + +static GLfloat light_pos[4] = {5.0, 5.0, 10.0, 0.0}; + +static void die(const char *msg) +{ + fprintf(stderr, "%s", msg); + exit(EXIT_FAILURE); +} + +static void +make_gear(const struct gear_template *t) +{ + GLint i; + GLfloat r0, r1, r2; + GLfloat angle, da; + GLfloat u, v, len; + + glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, t->material); + + r0 = t->inner_radius; + r1 = t->outer_radius - t->tooth_depth / 2.0; + r2 = t->outer_radius + t->tooth_depth / 2.0; + + da = 2.0 * M_PI / t->teeth / 4.0; + + glShadeModel(GL_FLAT); + + glNormal3f(0.0, 0.0, 1.0); + + /* draw front face */ + glBegin(GL_QUAD_STRIP); + for (i = 0; i <= t->teeth; i++) { + angle = i * 2.0 * M_PI / t->teeth; + glVertex3f(r0 * cos(angle), r0 * sin(angle), t->width * 0.5); + glVertex3f(r1 * cos(angle), r1 * sin(angle), t->width * 0.5); + if (i < t->teeth) { + glVertex3f(r0 * cos(angle), r0 * sin(angle), t->width * 0.5); + glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), t->width * 0.5); + } + } + glEnd(); + + /* draw front sides of teeth */ + glBegin(GL_QUADS); + da = 2.0 * M_PI / t->teeth / 4.0; + for (i = 0; i < t->teeth; i++) { + angle = i * 2.0 * M_PI / t->teeth; + + glVertex3f(r1 * cos(angle), r1 * sin(angle), t->width * 0.5); + glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), t->width * 0.5); + glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), t->width * 0.5); + glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), t->width * 0.5); + } + glEnd(); + + glNormal3f(0.0, 0.0, -1.0); + + /* draw back face */ + glBegin(GL_QUAD_STRIP); + for (i = 0; i <= t->teeth; i++) { + angle = i * 2.0 * M_PI / t->teeth; + glVertex3f(r1 * cos(angle), r1 * sin(angle), -t->width * 0.5); + glVertex3f(r0 * cos(angle), r0 * sin(angle), -t->width * 0.5); + if (i < t->teeth) { + glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -t->width * 0.5); + glVertex3f(r0 * cos(angle), r0 * sin(angle), -t->width * 0.5); + } + } + glEnd(); + + /* draw back sides of teeth */ + glBegin(GL_QUADS); + da = 2.0 * M_PI / t->teeth / 4.0; + for (i = 0; i < t->teeth; i++) { + angle = i * 2.0 * M_PI / t->teeth; + + glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -t->width * 0.5); + glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -t->width * 0.5); + glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -t->width * 0.5); + glVertex3f(r1 * cos(angle), r1 * sin(angle), -t->width * 0.5); + } + glEnd(); + + /* draw outward faces of teeth */ + glBegin(GL_QUAD_STRIP); + for (i = 0; i < t->teeth; i++) { + angle = i * 2.0 * M_PI / t->teeth; + + glVertex3f(r1 * cos(angle), r1 * sin(angle), t->width * 0.5); + glVertex3f(r1 * cos(angle), r1 * sin(angle), -t->width * 0.5); + u = r2 * cos(angle + da) - r1 * cos(angle); + v = r2 * sin(angle + da) - r1 * sin(angle); + len = sqrt(u * u + v * v); + u /= len; + v /= len; + glNormal3f(v, -u, 0.0); + glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), t->width * 0.5); + glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -t->width * 0.5); + glNormal3f(cos(angle), sin(angle), 0.0); + glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), t->width * 0.5); + glVertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -t->width * 0.5); + u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da); + v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da); + glNormal3f(v, -u, 0.0); + glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), t->width * 0.5); + glVertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -t->width * 0.5); + glNormal3f(cos(angle), sin(angle), 0.0); + } + + glVertex3f(r1 * cos(0), r1 * sin(0), t->width * 0.5); + glVertex3f(r1 * cos(0), r1 * sin(0), -t->width * 0.5); + + glEnd(); + + glShadeModel(GL_SMOOTH); + + /* draw inside radius cylinder */ + glBegin(GL_QUAD_STRIP); + for (i = 0; i <= t->teeth; i++) { + angle = i * 2.0 * M_PI / t->teeth; + glNormal3f(-cos(angle), -sin(angle), 0.0); + glVertex3f(r0 * cos(angle), r0 * sin(angle), -t->width * 0.5); + glVertex3f(r0 * cos(angle), r0 * sin(angle), t->width * 0.5); + } + glEnd(); +} + +static void +update_fps(struct gears *gears, uint32_t time) +{ + long diff_ms; + + gears->frames++; + + diff_ms = time - gears->last_fps; + + if (diff_ms > 5000) { + float seconds = diff_ms / 1000.0; + float fps = gears->frames / seconds; + + printf("%d frames in %6.3f seconds = %6.3f FPS\n", gears->frames, seconds, fps); + fflush(stdout); + + gears->frames = 0; + gears->last_fps = time; + } +} + +static void +frame_callback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct gears *gears = data; + + update_fps(gears, time); + + gears->angle = (GLfloat) (time % 8192) * 360 / 8192.0; + + window_schedule_redraw(gears->window); + + if (callback) + wl_callback_destroy(callback); +} + +static const struct wl_callback_listener listener = { + frame_callback +}; + +static int +motion_handler(struct widget *widget, struct input *input, + uint32_t time, float x, float y, void *data) +{ + struct gears *gears = data; + int offset_x, offset_y; + float step = 0.5; + + if (gears->button_down) { + offset_x = x - gears->last_x; + offset_y = y - gears->last_y; + gears->last_x = x; + gears->last_y = y; + gears->view.roty += offset_x * step; + gears->view.rotx += offset_y * step; + if (gears->view.roty >= 360) + gears->view.roty = gears->view.roty - 360; + if (gears->view.roty <= 0) + gears->view.roty = gears->view.roty + 360; + if (gears->view.rotx >= 360) + gears->view.rotx = gears->view.rotx - 360; + if (gears->view.rotx <= 0) + gears->view.rotx = gears->view.rotx + 360; + } + + return CURSOR_LEFT_PTR; +} + +static void +button_handler(struct widget *widget, struct input *input, + uint32_t time, uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct gears *gears = data; + + if (button == BTN_LEFT) { + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + gears->button_down = 1; + input_get_position(input, + &gears->last_x, &gears->last_y); + } else { + gears->button_down = 0; + } + } +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct rectangle window_allocation; + struct rectangle allocation; + struct wl_callback *callback; + struct gears *gears = data; + + widget_get_allocation(gears->widget, &allocation); + window_get_allocation(gears->window, &window_allocation); + + if (display_acquire_window_surface(gears->d, + gears->window, + gears->context) < 0) { + die("Unable to acquire window surface, " + "compiled without cairo-egl?\n"); + } + + glViewport(allocation.x, + window_allocation.height - allocation.height - allocation.y, + allocation.width, allocation.height); + glScissor(allocation.x, + window_allocation.height - allocation.height - allocation.y, + allocation.width, allocation.height); + + glEnable(GL_SCISSOR_TEST); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glPushMatrix(); + + glTranslatef(0.0, 0.0, -50); + + glRotatef(gears->view.rotx, 1.0, 0.0, 0.0); + glRotatef(gears->view.roty, 0.0, 1.0, 0.0); + + glPushMatrix(); + glTranslatef(-3.0, -2.0, 0.0); + glRotatef(gears->angle, 0.0, 0.0, 1.0); + glCallList(gears->gear_list[0]); + glPopMatrix(); + + glPushMatrix(); + glTranslatef(3.1, -2.0, 0.0); + glRotatef(-2.0 * gears->angle - 9.0, 0.0, 0.0, 1.0); + glCallList(gears->gear_list[1]); + glPopMatrix(); + + glPushMatrix(); + glTranslatef(-3.1, 4.2, 0.0); + glRotatef(-2.0 * gears->angle - 25.0, 0.0, 0.0, 1.0); + glCallList(gears->gear_list[2]); + glPopMatrix(); + + glPopMatrix(); + + glFlush(); + + display_release_window_surface(gears->d, gears->window); + + callback = wl_surface_frame(window_get_wl_surface(gears->window)); + wl_callback_add_listener(callback, &listener, gears); +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct gears *gears = data; + int32_t size, big, small; + + /* Constrain child size to be square and at least 300x300 */ + if (width < height) { + small = width; + big = height; + } else { + small = height; + big = width; + } + + if (gears->fullscreen) + size = small; + else + size = big; + + widget_set_size(gears->widget, size, size); +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + window_schedule_redraw(window); +} + +static void +fullscreen_handler(struct window *window, void *data) +{ + struct gears *gears = data; + + gears->fullscreen ^= 1; + window_set_fullscreen(window, gears->fullscreen); +} + +static struct gears * +gears_create(struct display *display) +{ + const int width = 450, height = 500; + struct gears *gears; + struct timeval tv; + int i; + + gears = zalloc(sizeof *gears); + gears->d = display; + gears->window = window_create(display); + gears->widget = frame_create(gears->window, gears); + window_set_title(gears->window, "Wayland Gears"); + + gears->display = display_get_egl_display(gears->d); + if (gears->display == NULL) + die("failed to create egl display\n"); + + eglBindAPI(EGL_OPENGL_API); + + gears->config = display_get_argb_egl_config(gears->d); + + gears->context = eglCreateContext(gears->display, gears->config, + EGL_NO_CONTEXT, NULL); + if (gears->context == NULL) + die("failed to create context\n"); + + if (!eglMakeCurrent(gears->display, NULL, NULL, gears->context)) + die("failed to make context current\n"); + + for (i = 0; i < 3; i++) { + gears->gear_list[i] = glGenLists(1); + glNewList(gears->gear_list[i], GL_COMPILE); + make_gear(&gear_templates[i]); + glEndList(); + } + + gears->button_down = 0; + gears->last_x = 0; + gears->last_y = 0; + + gears->view.rotx = 20.0; + gears->view.roty = 30.0; + + gettimeofday(&tv, NULL); + gears->last_fps = tv.tv_sec * 1000 + tv.tv_usec / 1000; + printf("Warning: FPS count is limited by the wayland compositor or monitor refresh rate\n"); + + glEnable(GL_NORMALIZE); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glFrustum(-1.0, 1.0, -1.0, 1.0, 5.0, 200.0); + glMatrixMode(GL_MODELVIEW); + + glLightfv(GL_LIGHT0, GL_POSITION, light_pos); + glEnable(GL_CULL_FACE); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_DEPTH_TEST); + glClearColor(0, 0, 0, 0.92); + + window_set_user_data(gears->window, gears); + widget_set_resize_handler(gears->widget, resize_handler); + widget_set_redraw_handler(gears->widget, redraw_handler); + widget_set_button_handler(gears->widget, button_handler); + widget_set_motion_handler(gears->widget, motion_handler); + window_set_keyboard_focus_handler(gears->window, + keyboard_focus_handler); + window_set_fullscreen_handler(gears->window, fullscreen_handler); + + window_schedule_resize(gears->window, width, height); + + return gears; +} + +int main(int argc, char *argv[]) +{ + struct display *d; + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + gears_create(d); + display_run(d); + + return 0; +} diff --git a/clients/glmatrix.c b/clients/glmatrix.c new file mode 100644 index 00000000..4c5754e9 --- /dev/null +++ b/clients/glmatrix.c @@ -0,0 +1,1062 @@ +/* glmatrix, Copyright (c) 2003, 2004 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + * + * GLMatrix -- simulate the text scrolls from the movie "The Matrix". + * + * This program does a 3D rendering of the dropping characters that + * appeared in the title sequences of the movies. See also `xmatrix' + * for a simulation of what the computer monitors actually *in* the + * movie did. + */ + +#define DEFAULTS "*delay: 30000 \n" \ + "*showFPS: False \n" \ + "*wireframe: False \n" \ + +# define refresh_matrix 0 +# define release_matrix 0 +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) + +#undef BELLRAND +#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3) + +#include "wscreensaver-glue.h" + +#ifdef __GNUC__ + __extension__ /* don't warn about "string length is greater than the length + ISO C89 compilers are required to support" when including + the following XPM file... */ +#endif +#include "matrix3.xpm" + + +#define DEF_SPEED "1.0" +#define DEF_DENSITY "20" +#define DEF_CLOCK "False" +#define DEF_FOG "True" +#define DEF_WAVES "True" +#define DEF_ROTATE "True" +#define DEF_TEXTURE "True" +#define DEF_MODE "Matrix" +#define DEF_TIMEFMT " %l%M%p " + + +#define CHAR_COLS 16 +#define CHAR_ROWS 13 + +static const int matrix_encoding[] = { + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, +# if 0 + 192, 193, 194, 195, 196, 197, 198, 199, + 200, 201, 202, 203, 204, 205, 206, 207 +# else + 160, 161, 162, 163, 164, 165, 166, 167, + 168, 169, 170, 171, 172, 173, 174, 175 +# endif + }; +static const int decimal_encoding[] = { + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }; +static const int hex_encoding[] = { + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 33, 34, 35, 36, 37, 38 }; +static const int binary_encoding[] = { 16, 17 }; +static const int dna_encoding[] = { 33, 35, 39, 52 }; + +static const unsigned char char_map[256] = { + 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, /* 0 */ + 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, /* 16 */ + 0, 1, 2, 96, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 32 */ + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, /* 48 */ + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, /* 64 */ + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, /* 80 */ + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, /* 96 */ + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, /* 112 */ + 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, /* 128 */ + 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, /* 144 */ + 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, /* 160 */ + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, /* 176 */ + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, /* 192 */ + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, /* 208 */ +#if 0 + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, /* 224 */ + 176,177,178,195,180,181,182,183,184,185,186,187,188,189,190,191 /* 240 */ +#else /* see spank_image() */ + 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, /* 224 */ + 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, /* 240 */ +#endif +}; + +#define CURSOR_GLYPH 97 + +/* #define DEBUG */ + +#define GRID_SIZE 70 /* width and height of the arena */ +#define GRID_DEPTH 35 /* depth of the arena */ +#define WAVE_SIZE 22 /* periodicity of color (brightness) waves */ +#define SPLASH_RATIO 0.7 /* ratio of GRID_DEPTH where chars hit the screen */ + +static const struct { GLfloat x, y; } nice_views[] = { + { 0, 0 }, + { 0, -20 }, /* this is a list of viewer rotations that look nice. */ + { 0, 20 }, /* every now and then we switch to a new one. */ + { 25, 0 }, /* (but we only use the first one at start-up.) */ + {-25, 0 }, + { 25, 20 }, + {-25, 20 }, + { 25, -20 }, + {-25, -20 }, + + { 10, 0 }, + {-10, 0 }, + { 0, 0 }, /* prefer these */ + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, +}; + + +typedef struct { + GLfloat x, y, z; /* position of strip */ + GLfloat dx, dy, dz; /* velocity of strip */ + + Bool erasing_p; /* Whether this strip is on its way out. */ + + int spinner_glyph; /* the bottommost glyph -- the feeder */ + GLfloat spinner_y; /* where on the strip the bottom glyph is */ + GLfloat spinner_speed; /* how fast the bottom glyph drops */ + + int glyphs[GRID_SIZE]; /* the other glyphs on the strip, which will be + revealed by the dropping spinner. + 0 means no glyph; negative means "spinner". + If non-zero, real value is abs(G)-1. */ + + Bool highlight[GRID_SIZE]; + /* some glyphs may be highlighted */ + + int spin_speed; /* Rotate all spinners every this-many frames */ + int spin_tick; /* frame counter */ + + int wave_position; /* Waves of brightness wash down the strip. */ + int wave_speed; /* every this-many frames. */ + int wave_tick; /* frame counter. */ + +} strip; + + +typedef struct { + GLXContext *glx_context; + Bool button_down_p; + GLuint texture; + int nstrips; + strip *strips; + const int *glyph_map; + int nglyphs; + GLfloat tex_char_width, tex_char_height; + + /* auto-tracking direction of view */ + int last_view, target_view; + GLfloat view_x, view_y; + int view_steps, view_tick; + Bool auto_tracking_p; + int track_tick; + + int real_char_rows; + GLfloat brightness_ramp[WAVE_SIZE]; + +} matrix_configuration; + +static matrix_configuration *mps = NULL; + +static GLfloat speed = 1.0; +static GLfloat density = 20.0; +static Bool do_clock; +static char *timefmt; +static Bool do_fog = 1; +static Bool do_waves; +static Bool do_rotate = 1; +static Bool do_texture = 1; +static char *mode_str; + +#if 0 +static XrmOptionDescRec opts[] = { + { "-speed", ".speed", XrmoptionSepArg, 0 }, + { "-density", ".density", XrmoptionSepArg, 0 }, + { "-mode", ".mode", XrmoptionSepArg, 0 }, + { "-binary", ".mode", XrmoptionNoArg, "binary" }, + { "-hexadecimal", ".mode", XrmoptionNoArg, "hexadecimal" }, + { "-decimal", ".mode", XrmoptionNoArg, "decimal" }, + { "-dna", ".mode", XrmoptionNoArg, "dna" }, + { "-clock", ".clock", XrmoptionNoArg, "True" }, + { "+clock", ".clock", XrmoptionNoArg, "False" }, + { "-timefmt", ".timefmt", XrmoptionSepArg, 0 }, + { "-fog", ".fog", XrmoptionNoArg, "True" }, + { "+fog", ".fog", XrmoptionNoArg, "False" }, + { "-waves", ".waves", XrmoptionNoArg, "True" }, + { "+waves", ".waves", XrmoptionNoArg, "False" }, + { "-rotate", ".rotate", XrmoptionNoArg, "True" }, + { "+rotate", ".rotate", XrmoptionNoArg, "False" }, + {"-texture", ".texture", XrmoptionNoArg, "True" }, + {"+texture", ".texture", XrmoptionNoArg, "False" }, +}; + +static argtype vars[] = { + {&mode_str, "mode", "Mode", DEF_MODE, t_String}, + {&speed, "speed", "Speed", DEF_SPEED, t_Float}, + {&density, "density", "Density", DEF_DENSITY, t_Float}, + {&do_clock, "clock", "Clock", DEF_CLOCK, t_Bool}, + {&timefmt, "timefmt", "Timefmt", DEF_TIMEFMT, t_String}, + {&do_fog, "fog", "Fog", DEF_FOG, t_Bool}, + {&do_waves, "waves", "Waves", DEF_WAVES, t_Bool}, + {&do_rotate, "rotate", "Rotate", DEF_ROTATE, t_Bool}, + {&do_texture, "texture", "Texture", DEF_TEXTURE, t_Bool}, +}; + +ENTRYPOINT ModeSpecOpt matrix_opts = {countof(opts), opts, countof(vars), vars, NULL}; +#endif + +/* Re-randomize the state of one strip. + */ +static void +reset_strip (ModeInfo *mi, strip *s) +{ + matrix_configuration *mp = &mps[MI_SCREEN(mi)]; + int i; + Bool time_displayed_p = False; /* never display time twice in one strip */ + + memset (s, 0, sizeof(*s)); + s->x = (GLfloat) (frand(GRID_SIZE) - (GRID_SIZE/2)); + s->y = (GLfloat) (GRID_SIZE/2 + BELLRAND(0.5)); /* shift top slightly */ + s->z = (GLfloat) (GRID_DEPTH * 0.2) - frand (GRID_DEPTH * 0.7); + s->spinner_y = 0; + + s->dx = 0; +/* s->dx = ((BELLRAND(0.01) - 0.005) * speed); */ + s->dy = 0; + s->dz = (BELLRAND(0.02) * speed); + + s->spinner_speed = (BELLRAND(0.3) * speed); + + s->spin_speed = (int) BELLRAND(2.0 / speed) + 1; + s->spin_tick = 0; + + s->wave_position = 0; + s->wave_speed = (int) BELLRAND(3.0 / speed) + 1; + s->wave_tick = 0; + + for (i = 0; i < GRID_SIZE; i++) + if (do_clock && + !time_displayed_p && + (i < GRID_SIZE-5) && /* display approx. once per 5 strips */ + !(random() % (GRID_SIZE-5)*5)) + { + unsigned int j; + char text[80]; + time_t now = time ((time_t *) 0); + struct tm *tm = localtime (&now); + strftime (text, sizeof(text)-1, timefmt, tm); + + /* render time into the strip */ + for (j = 0; j < strlen(text) && i < GRID_SIZE; j++, i++) + { + s->glyphs[i] = char_map [((unsigned char *) text)[j]] + 1; + s->highlight[i] = True; + } + + time_displayed_p = True; + } + else + { + int draw_p = (random() % 7); + int spin_p = (draw_p && !(random() % 20)); + int g = (draw_p + ? mp->glyph_map[(random() % mp->nglyphs)] + 1 + : 0); + if (spin_p) g = -g; + s->glyphs[i] = g; + s->highlight[i] = False; + } + + s->spinner_glyph = - (mp->glyph_map[(random() % mp->nglyphs)] + 1); +} + + +/* Animate the strip one step. Reset if it has reached the bottom. + */ +static void +tick_strip (ModeInfo *mi, strip *s) +{ + matrix_configuration *mp = &mps[MI_SCREEN(mi)]; + int i; + + if (mp->button_down_p) + return; + + s->x += s->dx; + s->y += s->dy; + s->z += s->dz; + + if (s->z > GRID_DEPTH * SPLASH_RATIO) /* splashed into screen */ + { + reset_strip (mi, s); + return; + } + + s->spinner_y += s->spinner_speed; + if (s->spinner_y >= GRID_SIZE) + { + if (s->erasing_p) + { + reset_strip (mi, s); + return; + } + else + { + s->erasing_p = True; + s->spinner_y = 0; + s->spinner_speed /= 2; /* erase it slower than we drew it */ + } + } + + /* Spin the spinners. */ + s->spin_tick++; + if (s->spin_tick > s->spin_speed) + { + s->spin_tick = 0; + s->spinner_glyph = - (mp->glyph_map[(random() % mp->nglyphs)] + 1); + for (i = 0; i < GRID_SIZE; i++) + if (s->glyphs[i] < 0) + { + s->glyphs[i] = -(mp->glyph_map[(random() % mp->nglyphs)] + 1); + if (! (random() % 800)) /* sometimes they stop spinning */ + s->glyphs[i] = -s->glyphs[i]; + } + } + + /* Move the color (brightness) wave. */ + s->wave_tick++; + if (s->wave_tick > s->wave_speed) + { + s->wave_tick = 0; + s->wave_position++; + if (s->wave_position >= WAVE_SIZE) + s->wave_position = 0; + } +} + + +/* Draw a single character at the given position and brightness. + */ +static void +draw_glyph (ModeInfo *mi, int glyph, Bool highlight, + GLfloat x, GLfloat y, GLfloat z, + GLfloat brightness) +{ + matrix_configuration *mp = &mps[MI_SCREEN(mi)]; + int wire = MI_IS_WIREFRAME(mi); + GLfloat w = mp->tex_char_width; + GLfloat h = mp->tex_char_height; + GLfloat cx = 0, cy = 0; + GLfloat S = 1; + Bool spinner_p = (glyph < 0); + + if (glyph == 0) abort(); + if (glyph < 0) glyph = -glyph; + + if (spinner_p) + brightness *= 1.5; + + if (!do_texture) + { + S = 0.8; + x += 0.1; + y += 0.1; + } + else + { + int ccx = ((glyph - 1) % CHAR_COLS); + int ccy = ((glyph - 1) / CHAR_COLS); + + cx = ccx * w; + cy = (mp->real_char_rows - ccy - 1) * h; + + if (do_fog) + { + GLfloat depth; + depth = (z / GRID_DEPTH) + 0.5; /* z ratio from back/front */ + depth = 0.2 + (depth * 0.8); /* scale to range [0.2 - 1.0] */ + brightness *= depth; /* so no row goes all black. */ + } + } + + { + GLfloat r, g, b, a; + + if (highlight) + brightness *= 2; + + if (!do_texture && !spinner_p) + r = b = 0, g = 1; + else + r = g = b = 1; + + a = brightness; + + /* If the glyph is very close to the screen (meaning it is very large, + and is about to splash into the screen and vanish) then start fading + it out, proportional to how close to the glass it is. + */ + if (z > GRID_DEPTH/2) + { + GLfloat ratio = ((z - GRID_DEPTH/2) / + ((GRID_DEPTH * SPLASH_RATIO) - GRID_DEPTH/2)); + int i = ratio * WAVE_SIZE; + + if (i < 0) i = 0; + else if (i >= WAVE_SIZE) i = WAVE_SIZE-1; + + a *= mp->brightness_ramp[i]; + } + + glColor4f (r,g,b,a); + } + + glBegin (wire ? GL_LINE_LOOP : GL_QUADS); + glNormal3f (0, 0, 1); + glTexCoord2f (cx, cy); glVertex3f (x, y, z); + glTexCoord2f (cx+w, cy); glVertex3f (x+S, y, z); + glTexCoord2f (cx+w, cy+h); glVertex3f (x+S, y+S, z); + glTexCoord2f (cx, cy+h); glVertex3f (x, y+S, z); + glEnd (); + + if (wire && spinner_p) + { + glBegin (GL_LINES); + glVertex3f (x, y, z); + glVertex3f (x+S, y+S, z); + glVertex3f (x, y+S, z); + glVertex3f (x+S, y, z); + glEnd(); + } + + mi->polygon_count++; +} + + +/* Draw all the visible glyphs in the strip. + */ +static void +draw_strip (ModeInfo *mi, strip *s) +{ + matrix_configuration *mp = &mps[MI_SCREEN(mi)]; + int i; + for (i = 0; i < GRID_SIZE; i++) + { + int g = s->glyphs[i]; + Bool below_p = (s->spinner_y >= i); + + if (s->erasing_p) + below_p = !below_p; + + if (g && below_p) /* don't draw cells below the spinner */ + { + GLfloat brightness; + if (!do_waves) + brightness = 1.0; + else + { + int j = WAVE_SIZE - ((i + (GRID_SIZE - s->wave_position)) + % WAVE_SIZE); + brightness = mp->brightness_ramp[j]; + } + + draw_glyph (mi, g, s->highlight[i], + s->x, s->y - i, s->z, brightness); + } + } + + if (!s->erasing_p) + draw_glyph (mi, s->spinner_glyph, False, + s->x, s->y - s->spinner_y, s->z, 1.0); +} + + +/* qsort comparator for sorting strips by z position */ +static int +cmp_strips (const void *aa, const void *bb) +{ + const strip *a = *(strip **) aa; + const strip *b = *(strip **) bb; + return ((int) (a->z * 10000) - + (int) (b->z * 10000)); +} + + +/* Auto-tracking + */ + +static void +auto_track_init (ModeInfo *mi) +{ + matrix_configuration *mp = &mps[MI_SCREEN(mi)]; + mp->last_view = 0; + mp->target_view = 0; + mp->view_x = nice_views[mp->last_view].x; + mp->view_y = nice_views[mp->last_view].y; + mp->view_steps = 100; + mp->view_tick = 0; + mp->auto_tracking_p = False; +} + + +static void +auto_track (ModeInfo *mi) +{ + matrix_configuration *mp = &mps[MI_SCREEN(mi)]; + + if (! do_rotate) + return; + if (mp->button_down_p) + return; + + /* if we're not moving, maybe start moving. Otherwise, do nothing. */ + if (! mp->auto_tracking_p) + { + if (++mp->track_tick < 20/speed) return; + mp->track_tick = 0; + if (! (random() % 20)) + mp->auto_tracking_p = True; + else + return; + } + + + { + GLfloat ox = nice_views[mp->last_view].x; + GLfloat oy = nice_views[mp->last_view].y; + GLfloat tx = nice_views[mp->target_view].x; + GLfloat ty = nice_views[mp->target_view].y; + + /* move from A to B with sinusoidal deltas, so that it doesn't jerk + to a stop. */ + GLfloat th = sin ((M_PI / 2) * (double) mp->view_tick / mp->view_steps); + + mp->view_x = (ox + ((tx - ox) * th)); + mp->view_y = (oy + ((ty - oy) * th)); + mp->view_tick++; + + if (mp->view_tick >= mp->view_steps) + { + mp->view_tick = 0; + mp->view_steps = (350.0 / speed); + mp->last_view = mp->target_view; + mp->target_view = (random() % (countof(nice_views) - 1)) + 1; + mp->auto_tracking_p = False; + } + } +} + + +/* Window management, etc + */ +ENTRYPOINT void +reshape_matrix (ModeInfo *mi, int width, int height) +{ + GLfloat h = (GLfloat) height / (GLfloat) width; + + glViewport (0, 0, (GLint) width, (GLint) height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective (80.0, 1/h, 1.0, 100); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt( 0.0, 0.0, 25.0, + 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0); +} + + +#if 0 +ENTRYPOINT Bool +matrix_handle_event (ModeInfo *mi, XEvent *event) +{ + matrix_configuration *mp = &mps[MI_SCREEN(mi)]; + + if (event->xany.type == ButtonPress && + event->xbutton.button == Button1) + { + mp->button_down_p = True; + return True; + } + else if (event->xany.type == ButtonRelease && + event->xbutton.button == Button1) + { + mp->button_down_p = False; + return True; + } + + return False; +} +#endif + +#if 0 +static Bool +bigendian (void) +{ + union { int i; char c[sizeof(int)]; } u; + u.i = 1; + return !u.c[0]; +} +#endif + + +/* The image with the characters in it is 512x598, meaning that it needs to + be copied into a 512x1024 texture. But some machines can't handle textures + that large... And it turns out that we aren't using most of the characters + in that image anyway, since this program doesn't do anything that makes use + of the full range of Latin1 characters. So... this function tosses out the + last 32 of the Latin1 characters, resulting in a 512x506 image, which we + can then stuff in a 512x512 texture. Voila. + + If this hack ever grows into something that displays full Latin1 text, + well then, Something Else Will Need To Be Done. + */ +static void +spank_image (matrix_configuration *mp, XImage *xi) +{ + int ch = xi->height / CHAR_ROWS; + int cut = 2; + unsigned char *bits = (unsigned char *) xi->data; + unsigned char *from, *to, *s, *end; + int L = xi->bytes_per_line * ch; +/* int i;*/ + + /* Copy row 12 into 10 (which really means, copy 2 into 0, + since texture data is upside down.). + */ + to = bits + (L * cut); + from = bits; + end = from + L; + s = from; + while (s < end) + *to++ = *s++; + + /* Then, pull all the bits down by 2 rows. + */ + to = bits; + from = bits + (L * cut); + end = bits + (L * CHAR_ROWS); + s = from; + while (s < end) + *to++ = *s++; + + /* And clear out the rest, for good measure. + */ + from = bits + (L * (CHAR_ROWS - cut)); + end = bits + (L * CHAR_ROWS); + s = from; + while (s < end) + *s++ = 0; + + xi->height -= (cut * ch); + mp->real_char_rows -= cut; + +# if 0 + /* Finally, pull the map indexes back to match the new bits. + */ + for (i = 0; i < countof(matrix_encoding); i++) + if (matrix_encoding[i] > (CHAR_COLS * (CHAR_ROWS - cut))) + matrix_encoding[i] -= (cut * CHAR_COLS); +# endif +} + + +static void +load_textures (ModeInfo *mi, Bool flip_p) +{ + matrix_configuration *mp = &mps[MI_SCREEN(mi)]; + XImage *xi; + int x, y; + int cw, ch; + int orig_w, orig_h; + + /* The Matrix XPM is 512x598 -- but GL texture sizes must be powers of 2. + So we waste some padding rows to round up. + */ + xi = xpm_to_ximage (matrix3_xpm); + orig_w = xi->width; + orig_h = xi->height; + mp->real_char_rows = CHAR_ROWS; + spank_image (mp, xi); + + if (xi->height != 512 && xi->height != 1024) + { + xi->height = (xi->height < 512 ? 512 : 1024); + xi->data = realloc (xi->data, xi->height * xi->bytes_per_line); + if (!xi->data) + { + fprintf(stderr, "%s: out of memory\n", progname); + exit(1); + } + } + + if (xi->width != 512) abort(); + if (xi->height != 512 && xi->height != 1024) abort(); + + /* char size in pixels */ + cw = orig_w / CHAR_COLS; + ch = orig_h / CHAR_ROWS; + + /* char size in ratio of final (padded) texture size */ + mp->tex_char_width = (GLfloat) cw / xi->width; + mp->tex_char_height = (GLfloat) ch / xi->height; + + /* Flip each character's bits horizontally -- we could also just do this + by reversing the texture coordinates on the quads, but on some systems + that slows things down a lot. + */ + if (flip_p) + { + int xx, col; + unsigned long buf[100]; + for (y = 0; y < xi->height; y++) + for (col = 0, xx = 0; col < CHAR_COLS; col++, xx += cw) + { + for (x = 0; x < cw; x++) + buf[x] = XGetPixel (xi, xx+x, y); + for (x = 0; x < cw; x++) + XPutPixel (xi, xx+x, y, buf[cw-x-1]); + } + } + + /* The pixmap is a color image with no transparency. Set the texture's + alpha to be the green channel, and set the green channel to be 100%. + */ + { + int rpos, gpos, bpos, apos; /* bitfield positions */ +#if 0 + /* #### Cherub says that the little-endian case must be taken on MacOSX, + or else the colors/alpha are the wrong way around. How can + that be the case? + */ + if (bigendian()) + rpos = 24, gpos = 16, bpos = 8, apos = 0; + else +#endif + rpos = 0, gpos = 8, bpos = 16, apos = 24; + + for (y = 0; y < xi->height; y++) + for (x = 0; x < xi->width; x++) + { + unsigned long p = XGetPixel (xi, x, y); + unsigned char r = (p >> rpos) & 0xFF; + unsigned char g = (p >> gpos) & 0xFF; + unsigned char b = (p >> bpos) & 0xFF; + unsigned char a = g; + g = 0xFF; + p = (r << rpos) | (g << gpos) | (b << bpos) | (a << apos); + XPutPixel (xi, x, y, p); + } + } + + /* Now load the texture into GL. + */ + clear_gl_error(); + glGenTextures (1, &mp->texture); + + glPixelStorei (GL_UNPACK_ALIGNMENT, 4); + glPixelStorei (GL_UNPACK_ROW_LENGTH, xi->width); + glBindTexture (GL_TEXTURE_2D, mp->texture); + check_gl_error ("texture init"); + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, xi->width, xi->height, 0, GL_RGBA, + GL_UNSIGNED_INT_8_8_8_8_REV, xi->data); + { + char buf[255]; + sprintf (buf, "creating %dx%d texture:", xi->width, xi->height); + check_gl_error (buf); + } + + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + /* I'd expect CLAMP to be the thing to do here, but oddly, we get a + faint solid green border around the texture if it is *not* REPEAT! + */ + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + glPixelStorei (GL_UNPACK_ALIGNMENT, 1); + check_gl_error ("texture param"); + + XDestroyImage (xi); +} + + +ENTRYPOINT void +init_matrix (ModeInfo *mi) +{ + matrix_configuration *mp; + int wire = MI_IS_WIREFRAME(mi); + Bool flip_p = 0; + int i; + + if (wire) + do_texture = False; + + if (!mps) { + mps = (matrix_configuration *) + calloc (MI_NUM_SCREENS(mi), sizeof (matrix_configuration)); + if (!mps) { + fprintf(stderr, "%s: out of memory\n", progname); + exit(1); + } + } + + mp = &mps[MI_SCREEN(mi)]; + mp->glx_context = init_GL(mi); + + if (!mode_str || !*mode_str || !strcasecmp(mode_str, "matrix")) + { + flip_p = 1; + mp->glyph_map = matrix_encoding; + mp->nglyphs = countof(matrix_encoding); + } + else if (!strcasecmp (mode_str, "dna")) + { + flip_p = 0; + mp->glyph_map = dna_encoding; + mp->nglyphs = countof(dna_encoding); + } + else if (!strcasecmp (mode_str, "bin") || + !strcasecmp (mode_str, "binary")) + { + flip_p = 0; + mp->glyph_map = binary_encoding; + mp->nglyphs = countof(binary_encoding); + } + else if (!strcasecmp (mode_str, "hex") || + !strcasecmp (mode_str, "hexadecimal")) + { + flip_p = 0; + mp->glyph_map = hex_encoding; + mp->nglyphs = countof(hex_encoding); + } + else if (!strcasecmp (mode_str, "dec") || + !strcasecmp (mode_str, "decimal")) + { + flip_p = 0; + mp->glyph_map = decimal_encoding; + mp->nglyphs = countof(decimal_encoding); + } + else + { + fprintf (stderr, + "%s: `mode' must be matrix, dna, binary, or hex: not `%s'\n", + progname, mode_str); + exit (1); + } + + reshape_matrix (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); + + glShadeModel(GL_SMOOTH); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glEnable(GL_NORMALIZE); + + if (do_texture) + { + load_textures (mi, flip_p); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + /* Jeff Epler points out: + By using GL_ONE instead of GL_SRC_ONE_MINUS_ALPHA, glyphs are + added to each other, so that a bright glyph with a darker one + in front is a little brighter than the bright glyph alone. + */ + glBlendFunc (GL_SRC_ALPHA, GL_ONE); + } + + /* to scale coverage-percent to strips, this number looks about right... */ + mp->nstrips = (int) (density * 2.2); + if (mp->nstrips < 1) mp->nstrips = 1; + else if (mp->nstrips > 2000) mp->nstrips = 2000; + + + mp->strips = calloc (mp->nstrips, sizeof(strip)); + for (i = 0; i < mp->nstrips; i++) + { + strip *s = &mp->strips[i]; + reset_strip (mi, s); + + /* If we start all strips from zero at once, then the first few seconds + of the animation are much denser than normal. So instead, set all + the initial strips to erase-mode with random starting positions. + As these die off at random speeds and are re-created, we'll get a + more consistent density. */ + s->erasing_p = True; + s->spinner_y = frand(GRID_SIZE); + memset (s->glyphs, 0, sizeof(s->glyphs)); /* no visible glyphs */ + } + + /* Compute the brightness ramp. + */ + for (i = 0; i < WAVE_SIZE; i++) + { + GLfloat j = ((WAVE_SIZE - i) / (GLfloat) (WAVE_SIZE - 1)); + j *= (M_PI / 2); /* j ranges from 0.0 - PI/2 */ + j = sin (j); /* j ranges from 0.0 - 1.0 */ + j = 0.2 + (j * 0.8); /* j ranges from 0.2 - 1.0 */ + mp->brightness_ramp[i] = j; + /* printf("%2d %8.2f\n", i, j); */ + } + + + auto_track_init (mi); +} + + +#ifdef DEBUG + +static void +draw_grid (ModeInfo *mi) +{ + if (!MI_IS_WIREFRAME(mi)) + { + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + } + glPushMatrix(); + + glColor3f(1, 1, 1); + glBegin(GL_LINES); + glVertex3f(-GRID_SIZE, 0, 0); glVertex3f(GRID_SIZE, 0, 0); + glVertex3f(0, -GRID_SIZE, 0); glVertex3f(0, GRID_SIZE, 0); + glEnd(); + glBegin(GL_LINE_LOOP); + glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, 0); + glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, 0); + glVertex3f( GRID_SIZE/2, GRID_SIZE/2, 0); + glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, 0); + glEnd(); + glBegin(GL_LINE_LOOP); + glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2); + glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, GRID_DEPTH/2); + glVertex3f( GRID_SIZE/2, GRID_SIZE/2, GRID_DEPTH/2); + glVertex3f( GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2); + glEnd(); + glBegin(GL_LINE_LOOP); + glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2); + glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, GRID_DEPTH/2); + glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, GRID_DEPTH/2); + glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2); + glEnd(); + glBegin(GL_LINES); + glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2); + glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2); + glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, GRID_DEPTH/2); + glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, GRID_DEPTH/2); + glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2); + glVertex3f( GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2); + glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, GRID_DEPTH/2); + glVertex3f( GRID_SIZE/2, GRID_SIZE/2, GRID_DEPTH/2); + glEnd(); + glPopMatrix(); + if (!MI_IS_WIREFRAME(mi)) + { + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + } +} +#endif /* DEBUG */ + + +ENTRYPOINT void +draw_matrix (ModeInfo *mi) +{ + matrix_configuration *mp = &mps[MI_SCREEN(mi)]; + int i; + + if (!mp->glx_context) + return; + + glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mp->glx_context)); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glPushMatrix (); + + if (do_rotate) + { + glRotatef (mp->view_x, 1, 0, 0); + glRotatef (mp->view_y, 0, 1, 0); + } + +#ifdef DEBUG +# if 0 + glScalef(0.5, 0.5, 0.5); +# endif +# if 0 + glRotatef(-30, 0, 1, 0); +# endif + draw_grid (mi); +#endif + + mi->polygon_count = 0; + + /* Render (and tick) each strip, starting at the back + (draw the ones farthest from the camera first, to make + the alpha transparency work out right.) + */ + { + strip **sorted = malloc (mp->nstrips * sizeof(*sorted)); + for (i = 0; i < mp->nstrips; i++) + sorted[i] = &mp->strips[i]; + qsort (sorted, i, sizeof(*sorted), cmp_strips); + + for (i = 0; i < mp->nstrips; i++) + { + strip *s = sorted[i]; + tick_strip (mi, s); + draw_strip (mi, s); + } + free (sorted); + } + + auto_track (mi); + +#if 0 + glBegin(GL_QUADS); + glColor3f(1,1,1); + glTexCoord2f (0,0); glVertex3f(-15,-15,0); + glTexCoord2f (0,1); glVertex3f(-15,15,0); + glTexCoord2f (1,1); glVertex3f(15,15,0); + glTexCoord2f (1,0); glVertex3f(15,-15,0); + glEnd(); +#endif + + glPopMatrix (); + + if (mi->fps_p) do_fps (mi); + glFinish(); + + glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi)); +} + +WL_EXPORT struct wscreensaver_plugin glmatrix_screensaver = { + "GLMatrix", + init_matrix, + draw_matrix, + reshape_matrix +}; diff --git a/clients/image.c b/clients/image.c new file mode 100644 index 00000000..4d3f3b87 --- /dev/null +++ b/clients/image.c @@ -0,0 +1,426 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2009 Chris Wilson + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "window.h" +#include "../shared/cairo-util.h" + +struct image { + struct window *window; + struct widget *widget; + struct display *display; + char *filename; + cairo_surface_t *image; + int fullscreen; + int *image_counter; + int32_t width, height; + + struct { + double x; + double y; + } pointer; + bool button_pressed; + + bool initialized; + cairo_matrix_t matrix; +}; + +static double +get_scale(struct image *image) +{ + assert(image->matrix.xy == 0.0 && + image->matrix.yx == 0.0 && + image->matrix.xx == image->matrix.yy); + return image->matrix.xx; +} + +static void +clamp_view(struct image *image) +{ + struct rectangle allocation; + double scale = get_scale(image); + double sw, sh; + + sw = image->width * scale; + sh = image->height * scale; + widget_get_allocation(image->widget, &allocation); + + if (sw < allocation.width) { + image->matrix.x0 = + (allocation.width - image->width * scale) / 2; + } else { + if (image->matrix.x0 > 0.0) + image->matrix.x0 = 0.0; + if (sw + image->matrix.x0 < allocation.width) + image->matrix.x0 = allocation.width - sw; + } + + if (sh < allocation.width) { + image->matrix.y0 = + (allocation.height - image->height * scale) / 2; + } else { + if (image->matrix.y0 > 0.0) + image->matrix.y0 = 0.0; + if (sh + image->matrix.y0 < allocation.height) + image->matrix.y0 = allocation.height - sh; + } +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct image *image = data; + struct rectangle allocation; + cairo_t *cr; + cairo_surface_t *surface; + double width, height, doc_aspect, window_aspect, scale; + cairo_matrix_t matrix; + cairo_matrix_t translate; + + surface = window_get_surface(image->window); + cr = cairo_create(surface); + widget_get_allocation(image->widget, &allocation); + cairo_rectangle(cr, allocation.x, allocation.y, + allocation.width, allocation.height); + cairo_clip(cr); + cairo_push_group(cr); + cairo_translate(cr, allocation.x, allocation.y); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_paint(cr); + + if (!image->initialized) { + image->initialized = true; + width = cairo_image_surface_get_width(image->image); + height = cairo_image_surface_get_height(image->image); + + doc_aspect = width / height; + window_aspect = (double) allocation.width / allocation.height; + if (doc_aspect < window_aspect) + scale = allocation.height / height; + else + scale = allocation.width / width; + + image->width = width; + image->height = height; + cairo_matrix_init_scale(&image->matrix, scale, scale); + + clamp_view(image); + } + + matrix = image->matrix; + cairo_matrix_init_translate(&translate, allocation.x, allocation.y); + cairo_matrix_multiply(&matrix, &matrix, &translate); + cairo_set_matrix(cr, &matrix); + + cairo_set_source_surface(cr, image->image, 0, 0); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_paint(cr); + + cairo_pop_group_to_source(cr); + cairo_paint(cr); + cairo_destroy(cr); + + cairo_surface_destroy(surface); +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct image *image = data; + + clamp_view(image); +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct image *image = data; + + window_schedule_redraw(image->window); +} + +static int +enter_handler(struct widget *widget, + struct input *input, + float x, float y, void *data) +{ + struct image *image = data; + struct rectangle allocation; + + widget_get_allocation(image->widget, &allocation); + x -= allocation.x; + y -= allocation.y; + + image->pointer.x = x; + image->pointer.y = y; + + return 1; +} + +static void +move_viewport(struct image *image, double dx, double dy) +{ + double scale = get_scale(image); + + if (!image->initialized) + return; + + cairo_matrix_translate(&image->matrix, -dx/scale, -dy/scale); + clamp_view(image); + + window_schedule_redraw(image->window); +} + +static int +motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct image *image = data; + struct rectangle allocation; + + widget_get_allocation(image->widget, &allocation); + x -= allocation.x; + y -= allocation.y; + + if (image->button_pressed) + move_viewport(image, image->pointer.x - x, + image->pointer.y - y); + + image->pointer.x = x; + image->pointer.y = y; + + return image->button_pressed ? CURSOR_DRAGGING : CURSOR_LEFT_PTR; +} + +static void +button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, + void *data) +{ + struct image *image = data; + + if (button == BTN_LEFT) { + image->button_pressed = + state == WL_POINTER_BUTTON_STATE_PRESSED; + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + input_set_pointer_image(input, CURSOR_DRAGGING); + else + input_set_pointer_image(input, CURSOR_LEFT_PTR); + } +} + +static void +zoom(struct image *image, double scale) +{ + double x = image->pointer.x; + double y = image->pointer.y; + cairo_matrix_t scale_matrix; + + if (!image->initialized) + return; + + if (get_scale(image) * scale > 20.0 || + get_scale(image) * scale < 0.02) + return; + + cairo_matrix_init_identity(&scale_matrix); + cairo_matrix_translate(&scale_matrix, x, y); + cairo_matrix_scale(&scale_matrix, scale, scale); + cairo_matrix_translate(&scale_matrix, -x, -y); + + cairo_matrix_multiply(&image->matrix, &image->matrix, &scale_matrix); + clamp_view(image); +} + +static void +key_handler(struct window *window, struct input *input, uint32_t time, + uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, + void *data) +{ + struct image *image = data; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + switch (sym) { + case XKB_KEY_minus: + zoom(image, 0.8); + window_schedule_redraw(image->window); + break; + case XKB_KEY_equal: + case XKB_KEY_plus: + zoom(image, 1.2); + window_schedule_redraw(image->window); + break; + case XKB_KEY_1: + image->matrix.xx = 1.0; + image->matrix.xy = 0.0; + image->matrix.yx = 0.0; + image->matrix.yy = 1.0; + clamp_view(image); + window_schedule_redraw(image->window); + break; + } +} + +static void +axis_handler(struct widget *widget, struct input *input, uint32_t time, + uint32_t axis, wl_fixed_t value, void *data) +{ + struct image *image = data; + + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL && + input_get_modifiers(input) == MOD_CONTROL_MASK) { + /* set zoom level to 2% per 10 axis units */ + zoom(image, (1.0 - wl_fixed_to_double(value) / 500.0)); + + window_schedule_redraw(image->window); + } else if (input_get_modifiers(input) == 0) { + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) + move_viewport(image, 0, wl_fixed_to_double(value)); + else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) + move_viewport(image, wl_fixed_to_double(value), 0); + } +} + +static void +fullscreen_handler(struct window *window, void *data) +{ + struct image *image = data; + + image->fullscreen ^= 1; + window_set_fullscreen(window, image->fullscreen); +} + +static void +close_handler(struct window *window, void *data) +{ + struct image *image = data; + + *image->image_counter -= 1; + + if (*image->image_counter == 0) + display_exit(image->display); + + widget_destroy(image->widget); + window_destroy(image->window); + + free(image); +} + +static struct image * +image_create(struct display *display, const char *filename, + int *image_counter) +{ + struct image *image; + char *b, *copy, title[512];; + + image = zalloc(sizeof *image); + if (image == NULL) + return image; + + copy = strdup(filename); + b = basename(copy); + snprintf(title, sizeof title, "Wayland Image - %s", b); + free(copy); + + image->filename = strdup(filename); + image->image = load_cairo_surface(filename); + + if (!image->image) { + fprintf(stderr, "could not find the image %s!\n", b); + free(image->filename); + free(image); + return NULL; + } + + image->window = window_create(display); + image->widget = frame_create(image->window, image); + window_set_title(image->window, title); + image->display = display; + image->image_counter = image_counter; + *image_counter += 1; + image->initialized = false; + + window_set_user_data(image->window, image); + widget_set_redraw_handler(image->widget, redraw_handler); + widget_set_resize_handler(image->widget, resize_handler); + window_set_keyboard_focus_handler(image->window, + keyboard_focus_handler); + window_set_fullscreen_handler(image->window, fullscreen_handler); + window_set_close_handler(image->window, close_handler); + + widget_set_enter_handler(image->widget, enter_handler); + widget_set_motion_handler(image->widget, motion_handler); + widget_set_button_handler(image->widget, button_handler); + widget_set_axis_handler(image->widget, axis_handler); + window_set_key_handler(image->window, key_handler); + widget_schedule_resize(image->widget, 500, 400); + + return image; +} + +int +main(int argc, char *argv[]) +{ + struct display *d; + int i; + int image_counter = 0; + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + for (i = 1; i < argc; i++) + image_create(d, argv[i], &image_counter); + + if (image_counter > 0) + display_run(d); + + return 0; +} diff --git a/clients/keyboard.c b/clients/keyboard.c new file mode 100644 index 00000000..9ee4a848 --- /dev/null +++ b/clients/keyboard.c @@ -0,0 +1,922 @@ +/* + * Copyright © 2012 Openismus GmbH + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +#include "window.h" +#include "input-method-client-protocol.h" +#include "text-client-protocol.h" + +struct keyboard; + +struct virtual_keyboard { + struct wl_input_panel *input_panel; + struct wl_input_method *input_method; + struct wl_input_method_context *context; + struct display *display; + struct output *output; + char *preedit_string; + uint32_t preedit_style; + struct { + xkb_mod_mask_t shift_mask; + } keysym; + uint32_t serial; + uint32_t content_hint; + uint32_t content_purpose; + char *preferred_language; + char *surrounding_text; + uint32_t surrounding_cursor; + struct keyboard *keyboard; +}; + +enum key_type { + keytype_default, + keytype_backspace, + keytype_enter, + keytype_space, + keytype_switch, + keytype_symbols, + keytype_tab, + keytype_arrow_up, + keytype_arrow_left, + keytype_arrow_right, + keytype_arrow_down, + keytype_style +}; + +struct key { + enum key_type key_type; + + char *label; + char *alt; + + unsigned int width; +}; + +struct layout { + const struct key *keys; + uint32_t count; + + uint32_t columns; + uint32_t rows; + + const char *language; + uint32_t text_direction; +}; + +static const struct key normal_keys[] = { + { keytype_default, "q", "Q", 1}, + { keytype_default, "w", "W", 1}, + { keytype_default, "e", "E", 1}, + { keytype_default, "r", "R", 1}, + { keytype_default, "t", "T", 1}, + { keytype_default, "y", "Y", 1}, + { keytype_default, "u", "U", 1}, + { keytype_default, "i", "I", 1}, + { keytype_default, "o", "O", 1}, + { keytype_default, "p", "P", 1}, + { keytype_backspace, "<--", "<--", 2}, + + { keytype_tab, "->|", "->|", 1}, + { keytype_default, "a", "A", 1}, + { keytype_default, "s", "S", 1}, + { keytype_default, "d", "D", 1}, + { keytype_default, "f", "F", 1}, + { keytype_default, "g", "G", 1}, + { keytype_default, "h", "H", 1}, + { keytype_default, "j", "J", 1}, + { keytype_default, "k", "K", 1}, + { keytype_default, "l", "L", 1}, + { keytype_enter, "Enter", "Enter", 2}, + + { keytype_switch, "ABC", "abc", 2}, + { keytype_default, "z", "Z", 1}, + { keytype_default, "x", "X", 1}, + { keytype_default, "c", "C", 1}, + { keytype_default, "v", "V", 1}, + { keytype_default, "b", "B", 1}, + { keytype_default, "n", "N", 1}, + { keytype_default, "m", "M", 1}, + { keytype_default, ",", ",", 1}, + { keytype_default, ".", ".", 1}, + { keytype_switch, "ABC", "abc", 1}, + + { keytype_symbols, "?123", "?123", 1}, + { keytype_space, "", "", 5}, + { keytype_arrow_up, "/\\", "/\\", 1}, + { keytype_arrow_left, "<", "<", 1}, + { keytype_arrow_right, ">", ">", 1}, + { keytype_arrow_down, "\\/", "\\/", 1}, + { keytype_style, "", "", 2} +}; + +static const struct key numeric_keys[] = { + { keytype_default, "1", "1", 1}, + { keytype_default, "2", "2", 1}, + { keytype_default, "3", "3", 1}, + { keytype_default, "4", "4", 1}, + { keytype_default, "5", "5", 1}, + { keytype_default, "6", "6", 1}, + { keytype_default, "7", "7", 1}, + { keytype_default, "8", "8", 1}, + { keytype_default, "9", "9", 1}, + { keytype_default, "0", "0", 1}, + { keytype_backspace, "<--", "<--", 2}, + + { keytype_space, "", "", 4}, + { keytype_enter, "Enter", "Enter", 2}, + { keytype_arrow_up, "/\\", "/\\", 1}, + { keytype_arrow_left, "<", "<", 1}, + { keytype_arrow_right, ">", ">", 1}, + { keytype_arrow_down, "\\/", "\\/", 1}, + { keytype_style, "", "", 2} +}; + +static const struct key arabic_keys[] = { + { keytype_default, "ض", "ض", 1}, + { keytype_default, "ص", "ص", 1}, + { keytype_default, "ث", "ث", 1}, + { keytype_default, "ق", "ق", 1}, + { keytype_default, "ف", "ف", 1}, + { keytype_default, "غ", "إ", 1}, + { keytype_default, "ع", "ع", 1}, + { keytype_default, "ه", "ه", 1}, + { keytype_default, "خ", "خ", 1}, + { keytype_default, "ح", "ح", 1}, + { keytype_default, "ج", "ج", 1}, + { keytype_backspace, "-->", "-->", 2}, + + { keytype_tab, "->|", "->|", 1}, + { keytype_default, "ش", "ش", 1}, + { keytype_default, "س", "س", 1}, + { keytype_default, "ي", "ي", 1}, + { keytype_default, "ب", "ب", 1}, + { keytype_default, "ل", "ل", 1}, + { keytype_default, "ا", "أ", 1}, + { keytype_default, "ت", "ت", 1}, + { keytype_default, "ن", "ن", 1}, + { keytype_default, "م", "م", 1}, + { keytype_default, "ك", "ك", 1}, + { keytype_default, "د", "د", 1}, + { keytype_enter, "Enter", "Enter", 2}, + + { keytype_switch, "ABC", "abc", 2}, + { keytype_default, "ئ", "ئ", 1}, + { keytype_default, "ء", "ء", 1}, + { keytype_default, "ؤ", "ؤ", 1}, + { keytype_default, "ر", "ر", 1}, + { keytype_default, "ى", "آ", 1}, + { keytype_default, "ة", "ة", 1}, + { keytype_default, "و", "و", 1}, + { keytype_default, "ز", "ز", 1}, + { keytype_default, "ظ", "ظ", 1}, + { keytype_switch, "ABC", "abc", 2}, + + { keytype_symbols, "؟٣٢١", "؟٣٢١", 1}, + { keytype_default, "ذ", "ذ", 1}, + { keytype_default, "،", "،", 1}, + { keytype_space, "", "", 6}, + { keytype_default, ".", ".", 1}, + { keytype_default, "ط", "ط", 1}, + { keytype_style, "", "", 2} +}; + + +static const struct layout normal_layout = { + normal_keys, + sizeof(normal_keys) / sizeof(*normal_keys), + 12, + 4, + "en", + WL_TEXT_INPUT_TEXT_DIRECTION_LTR +}; + +static const struct layout numeric_layout = { + numeric_keys, + sizeof(numeric_keys) / sizeof(*numeric_keys), + 12, + 2, + "en", + WL_TEXT_INPUT_TEXT_DIRECTION_LTR +}; + +static const struct layout arabic_layout = { + arabic_keys, + sizeof(arabic_keys) / sizeof(*arabic_keys), + 13, + 4, + "ar", + WL_TEXT_INPUT_TEXT_DIRECTION_RTL +}; + +static const char *style_labels[] = { + "default", + "none", + "active", + "inactive", + "highlight", + "underline", + "selection", + "incorrect" +}; + +static const double key_width = 60; +static const double key_height = 50; + +enum keyboard_state { + keyboardstate_default, + keyboardstate_uppercase +}; + +struct keyboard { + struct virtual_keyboard *keyboard; + struct window *window; + struct widget *widget; + + enum keyboard_state state; +}; + +static const char * +label_from_key(struct keyboard *keyboard, + const struct key *key) +{ + if (key->key_type == keytype_style) + return style_labels[keyboard->keyboard->preedit_style]; + + if (keyboard->state == keyboardstate_default) + return key->label; + else + return key->alt; +} + +static void +draw_key(struct keyboard *keyboard, + const struct key *key, + cairo_t *cr, + unsigned int row, + unsigned int col) +{ + const char *label; + cairo_text_extents_t extents; + + cairo_save(cr); + cairo_rectangle(cr, + col * key_width, row * key_height, + key->width * key_width, key_height); + cairo_clip(cr); + + /* Paint frame */ + cairo_rectangle(cr, + col * key_width, row * key_height, + key->width * key_width, key_height); + cairo_set_line_width(cr, 3); + cairo_stroke(cr); + + /* Paint text */ + label = label_from_key(keyboard, key); + cairo_text_extents(cr, label, &extents); + + cairo_translate(cr, + col * key_width, + row * key_height); + cairo_translate(cr, + (key->width * key_width - extents.width) / 2, + (key_height - extents.y_bearing) / 2); + cairo_show_text(cr, label); + + cairo_restore(cr); +} + +static const struct layout * +get_current_layout(struct virtual_keyboard *keyboard) +{ + switch (keyboard->content_purpose) { + case WL_TEXT_INPUT_CONTENT_PURPOSE_DIGITS: + case WL_TEXT_INPUT_CONTENT_PURPOSE_NUMBER: + return &numeric_layout; + default: + if (keyboard->preferred_language && + strcmp(keyboard->preferred_language, "ar") == 0) + return &arabic_layout; + else + return &normal_layout; + } +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct keyboard *keyboard = data; + cairo_surface_t *surface; + struct rectangle allocation; + cairo_t *cr; + unsigned int i; + unsigned int row = 0, col = 0; + const struct layout *layout; + + layout = get_current_layout(keyboard->keyboard); + + surface = window_get_surface(keyboard->window); + widget_get_allocation(keyboard->widget, &allocation); + + cr = cairo_create(surface); + cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); + cairo_clip(cr); + + cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size(cr, 16); + + cairo_translate(cr, allocation.x, allocation.y); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 1, 1, 1, 0.75); + cairo_rectangle(cr, 0, 0, layout->columns * key_width, layout->rows * key_height); + cairo_paint(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + for (i = 0; i < layout->count; ++i) { + cairo_set_source_rgb(cr, 0, 0, 0); + draw_key(keyboard, &layout->keys[i], cr, row, col); + col += layout->keys[i].width; + if (col >= layout->columns) { + row += 1; + col = 0; + } + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + /* struct keyboard *keyboard = data; */ +} + +static char * +insert_text(const char *text, uint32_t offset, const char *insert) +{ + char *new_text = xmalloc(strlen(text) + strlen(insert) + 1); + + strncat(new_text, text, offset); + new_text[offset] = '\0'; + strcat(new_text, insert); + strcat(new_text, text + offset); + + return new_text; +} + +static void +virtual_keyboard_commit_preedit(struct virtual_keyboard *keyboard) +{ + char *surrounding_text; + + if (!keyboard->preedit_string || + strlen(keyboard->preedit_string) == 0) + return; + + wl_input_method_context_cursor_position(keyboard->context, + 0, 0); + wl_input_method_context_commit_string(keyboard->context, + keyboard->serial, + keyboard->preedit_string); + + if (keyboard->surrounding_text) { + surrounding_text = insert_text(keyboard->surrounding_text, + keyboard->surrounding_cursor, + keyboard->preedit_string); + free(keyboard->surrounding_text); + keyboard->surrounding_text = surrounding_text; + keyboard->surrounding_cursor += strlen(keyboard->preedit_string); + } else { + keyboard->surrounding_text = strdup(keyboard->preedit_string); + keyboard->surrounding_cursor = strlen(keyboard->preedit_string); + } + + free(keyboard->preedit_string); + keyboard->preedit_string = strdup(""); +} + +static void +virtual_keyboard_send_preedit(struct virtual_keyboard *keyboard, + int32_t cursor) +{ + uint32_t index = strlen(keyboard->preedit_string); + + if (keyboard->preedit_style) + wl_input_method_context_preedit_styling(keyboard->context, + 0, + strlen(keyboard->preedit_string), + keyboard->preedit_style); + if (cursor > 0) + index = cursor; + wl_input_method_context_preedit_cursor(keyboard->context, + index); + wl_input_method_context_preedit_string(keyboard->context, + keyboard->serial, + keyboard->preedit_string, + keyboard->preedit_string); +} + +static const char * +prev_utf8_char(const char *s, const char *p) +{ + for (--p; p >= s; --p) { + if ((*p & 0xc0) != 0x80) + return p; + } + return NULL; +} + +static void +delete_before_cursor(struct virtual_keyboard *keyboard) +{ + const char *start, *end; + + if (!keyboard->surrounding_text) { + fprintf(stderr, "delete_before_cursor: No surrounding text available\n"); + return; + } + + start = prev_utf8_char(keyboard->surrounding_text, + keyboard->surrounding_text + keyboard->surrounding_cursor); + if (!start) { + fprintf(stderr, "delete_before_cursor: No previous character to delete\n"); + return; + } + + end = keyboard->surrounding_text + keyboard->surrounding_cursor; + + wl_input_method_context_delete_surrounding_text(keyboard->context, + (start - keyboard->surrounding_text) - keyboard->surrounding_cursor, + end - start); + wl_input_method_context_commit_string(keyboard->context, + keyboard->serial, + ""); + + /* Update surrounding text */ + keyboard->surrounding_cursor = start - keyboard->surrounding_text; + keyboard->surrounding_text[keyboard->surrounding_cursor] = '\0'; + if (*end) + memmove(keyboard->surrounding_text + keyboard->surrounding_cursor, end, strlen(end)); +} + +static void +keyboard_handle_key(struct keyboard *keyboard, uint32_t time, const struct key *key, struct input *input, enum wl_pointer_button_state state) +{ + const char *label = keyboard->state == keyboardstate_default ? key->label : key->alt; + xkb_mod_mask_t mod_mask = keyboard->state == keyboardstate_default ? 0 : keyboard->keyboard->keysym.shift_mask; + uint32_t key_state = (state == WL_POINTER_BUTTON_STATE_PRESSED) ? WL_KEYBOARD_KEY_STATE_PRESSED : WL_KEYBOARD_KEY_STATE_RELEASED; + + switch (key->key_type) { + case keytype_default: + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + break; + + keyboard->keyboard->preedit_string = strcat(keyboard->keyboard->preedit_string, + label); + virtual_keyboard_send_preedit(keyboard->keyboard, -1); + break; + case keytype_backspace: + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + break; + + if (strlen(keyboard->keyboard->preedit_string) == 0) { + delete_before_cursor(keyboard->keyboard); + } else { + keyboard->keyboard->preedit_string[strlen(keyboard->keyboard->preedit_string) - 1] = '\0'; + virtual_keyboard_send_preedit(keyboard->keyboard, -1); + } + break; + case keytype_enter: + virtual_keyboard_commit_preedit(keyboard->keyboard); + wl_input_method_context_keysym(keyboard->keyboard->context, + display_get_serial(keyboard->keyboard->display), + time, + XKB_KEY_Return, key_state, mod_mask); + break; + case keytype_space: + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + break; + keyboard->keyboard->preedit_string = strcat(keyboard->keyboard->preedit_string, + " "); + virtual_keyboard_commit_preedit(keyboard->keyboard); + break; + case keytype_switch: + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + break; + if (keyboard->state == keyboardstate_default) + keyboard->state = keyboardstate_uppercase; + else + keyboard->state = keyboardstate_default; + break; + case keytype_symbols: + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + break; + break; + case keytype_tab: + virtual_keyboard_commit_preedit(keyboard->keyboard); + wl_input_method_context_keysym(keyboard->keyboard->context, + display_get_serial(keyboard->keyboard->display), + time, + XKB_KEY_Tab, key_state, mod_mask); + break; + case keytype_arrow_up: + virtual_keyboard_commit_preedit(keyboard->keyboard); + wl_input_method_context_keysym(keyboard->keyboard->context, + display_get_serial(keyboard->keyboard->display), + time, + XKB_KEY_Up, key_state, mod_mask); + break; + case keytype_arrow_left: + virtual_keyboard_commit_preedit(keyboard->keyboard); + wl_input_method_context_keysym(keyboard->keyboard->context, + display_get_serial(keyboard->keyboard->display), + time, + XKB_KEY_Left, key_state, mod_mask); + break; + case keytype_arrow_right: + virtual_keyboard_commit_preedit(keyboard->keyboard); + wl_input_method_context_keysym(keyboard->keyboard->context, + display_get_serial(keyboard->keyboard->display), + time, + XKB_KEY_Right, key_state, mod_mask); + break; + case keytype_arrow_down: + virtual_keyboard_commit_preedit(keyboard->keyboard); + wl_input_method_context_keysym(keyboard->keyboard->context, + display_get_serial(keyboard->keyboard->display), + time, + XKB_KEY_Down, key_state, mod_mask); + break; + case keytype_style: + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + break; + keyboard->keyboard->preedit_style = (keyboard->keyboard->preedit_style + 1) % 8; /* TODO */ + virtual_keyboard_send_preedit(keyboard->keyboard, -1); + break; + } +} + +static void +button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct keyboard *keyboard = data; + struct rectangle allocation; + int32_t x, y; + int row, col; + unsigned int i; + const struct layout *layout; + + layout = get_current_layout(keyboard->keyboard); + + if (button != BTN_LEFT) { + return; + } + + input_get_position(input, &x, &y); + + widget_get_allocation(keyboard->widget, &allocation); + x -= allocation.x; + y -= allocation.y; + + row = y / key_height; + col = x / key_width + row * layout->columns; + for (i = 0; i < layout->count; ++i) { + col -= layout->keys[i].width; + if (col < 0) { + keyboard_handle_key(keyboard, time, &layout->keys[i], input, state); + break; + } + } + + widget_schedule_redraw(widget); +} + +static void +touch_down_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float x, float y, void *data) +{ + + struct keyboard *keyboard = data; + struct rectangle allocation; + int row, col; + unsigned int i; + const struct layout *layout; + + layout = get_current_layout(keyboard->keyboard); + + widget_get_allocation(keyboard->widget, &allocation); + + x -= allocation.x; + y -= allocation.y; + + row = (int)y / key_height; + col = (int)x / key_width + row * layout->columns; + for (i = 0; i < layout->count; ++i) { + col -= layout->keys[i].width; + if (col < 0) { + keyboard_handle_key(keyboard, time, &layout->keys[i], input, WL_POINTER_BUTTON_STATE_PRESSED); + break; + } + } + + widget_schedule_redraw(widget); +} + +static void +touch_up_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + void *data) +{ + +} + +static void +handle_surrounding_text(void *data, + struct wl_input_method_context *context, + const char *text, + uint32_t cursor, + uint32_t anchor) +{ + struct virtual_keyboard *keyboard = data; + + free(keyboard->surrounding_text); + keyboard->surrounding_text = strdup(text); + + keyboard->surrounding_cursor = cursor; +} + +static void +handle_reset(void *data, + struct wl_input_method_context *context) +{ + struct virtual_keyboard *keyboard = data; + + fprintf(stderr, "Reset pre-edit buffer\n"); + + if (strlen(keyboard->preedit_string)) { + free(keyboard->preedit_string); + keyboard->preedit_string = strdup(""); + } +} + +static void +handle_content_type(void *data, + struct wl_input_method_context *context, + uint32_t hint, + uint32_t purpose) +{ + struct virtual_keyboard *keyboard = data; + + keyboard->content_hint = hint; + keyboard->content_purpose = purpose; +} + +static void +handle_invoke_action(void *data, + struct wl_input_method_context *context, + uint32_t button, + uint32_t index) +{ + struct virtual_keyboard *keyboard = data; + + if (button != BTN_LEFT) + return; + + virtual_keyboard_send_preedit(keyboard, index); +} + +static void +handle_commit_state(void *data, + struct wl_input_method_context *context, + uint32_t serial) +{ + struct virtual_keyboard *keyboard = data; + const struct layout *layout; + + keyboard->serial = serial; + + layout = get_current_layout(keyboard); + + if (keyboard->surrounding_text) + fprintf(stderr, "Surrounding text updated: %s\n", keyboard->surrounding_text); + + window_schedule_resize(keyboard->keyboard->window, + layout->columns * key_width, + layout->rows * key_height); + + wl_input_method_context_language(context, keyboard->serial, layout->language); + wl_input_method_context_text_direction(context, keyboard->serial, layout->text_direction); + + widget_schedule_redraw(keyboard->keyboard->widget); +} + +static void +handle_preferred_language(void *data, + struct wl_input_method_context *context, + const char *language) +{ + struct virtual_keyboard *keyboard = data; + + if (keyboard->preferred_language) + free(keyboard->preferred_language); + + keyboard->preferred_language = NULL; + + if (language) + keyboard->preferred_language = strdup(language); +} + +static const struct wl_input_method_context_listener input_method_context_listener = { + handle_surrounding_text, + handle_reset, + handle_content_type, + handle_invoke_action, + handle_commit_state, + handle_preferred_language +}; + +static void +input_method_activate(void *data, + struct wl_input_method *input_method, + struct wl_input_method_context *context) +{ + struct virtual_keyboard *keyboard = data; + struct wl_array modifiers_map; + const struct layout *layout; + + keyboard->keyboard->state = keyboardstate_default; + + if (keyboard->context) + wl_input_method_context_destroy(keyboard->context); + + if (keyboard->preedit_string) + free(keyboard->preedit_string); + + keyboard->preedit_string = strdup(""); + keyboard->content_hint = 0; + keyboard->content_purpose = 0; + free(keyboard->preferred_language); + keyboard->preferred_language = NULL; + free(keyboard->surrounding_text); + keyboard->surrounding_text = NULL; + + keyboard->serial = 0; + + keyboard->context = context; + wl_input_method_context_add_listener(context, + &input_method_context_listener, + keyboard); + + wl_array_init(&modifiers_map); + keysym_modifiers_add(&modifiers_map, "Shift"); + keysym_modifiers_add(&modifiers_map, "Control"); + keysym_modifiers_add(&modifiers_map, "Mod1"); + wl_input_method_context_modifiers_map(context, &modifiers_map); + keyboard->keysym.shift_mask = keysym_modifiers_get_mask(&modifiers_map, "Shift"); + wl_array_release(&modifiers_map); + + layout = get_current_layout(keyboard); + + window_schedule_resize(keyboard->keyboard->window, + layout->columns * key_width, + layout->rows * key_height); + + wl_input_method_context_language(context, keyboard->serial, layout->language); + wl_input_method_context_text_direction(context, keyboard->serial, layout->text_direction); + + widget_schedule_redraw(keyboard->keyboard->widget); +} + +static void +input_method_deactivate(void *data, + struct wl_input_method *input_method, + struct wl_input_method_context *context) +{ + struct virtual_keyboard *keyboard = data; + + if (!keyboard->context) + return; + + wl_input_method_context_destroy(keyboard->context); + keyboard->context = NULL; +} + +static const struct wl_input_method_listener input_method_listener = { + input_method_activate, + input_method_deactivate +}; + +static void +global_handler(struct display *display, uint32_t name, + const char *interface, uint32_t version, void *data) +{ + struct virtual_keyboard *keyboard = data; + + if (!strcmp(interface, "wl_input_panel")) { + keyboard->input_panel = + display_bind(display, name, &wl_input_panel_interface, 1); + } else if (!strcmp(interface, "wl_input_method")) { + keyboard->input_method = + display_bind(display, name, + &wl_input_method_interface, 1); + wl_input_method_add_listener(keyboard->input_method, &input_method_listener, keyboard); + } +} + +static void +keyboard_create(struct output *output, struct virtual_keyboard *virtual_keyboard) +{ + struct keyboard *keyboard; + const struct layout *layout; + struct wl_input_panel_surface *ips; + + layout = get_current_layout(virtual_keyboard); + + keyboard = xzalloc(sizeof *keyboard); + keyboard->keyboard = virtual_keyboard; + keyboard->window = window_create_custom(virtual_keyboard->display); + keyboard->widget = window_add_widget(keyboard->window, keyboard); + + virtual_keyboard->keyboard = keyboard; + + window_set_title(keyboard->window, "Virtual keyboard"); + window_set_user_data(keyboard->window, keyboard); + + widget_set_redraw_handler(keyboard->widget, redraw_handler); + widget_set_resize_handler(keyboard->widget, resize_handler); + widget_set_button_handler(keyboard->widget, button_handler); + widget_set_touch_down_handler(keyboard->widget, touch_down_handler); + widget_set_touch_up_handler(keyboard->widget, touch_up_handler); + + + window_schedule_resize(keyboard->window, + layout->columns * key_width, + layout->rows * key_height); + + + ips = wl_input_panel_get_input_panel_surface(virtual_keyboard->input_panel, + window_get_wl_surface(keyboard->window)); + + wl_input_panel_surface_set_toplevel(ips, + output_get_wl_output(output), + WL_INPUT_PANEL_SURFACE_POSITION_CENTER_BOTTOM); +} + +int +main(int argc, char *argv[]) +{ + struct virtual_keyboard virtual_keyboard; + struct output *output; + + memset(&virtual_keyboard, 0, sizeof virtual_keyboard); + + virtual_keyboard.display = display_create(&argc, argv); + if (virtual_keyboard.display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + display_set_user_data(virtual_keyboard.display, &virtual_keyboard); + display_set_global_handler(virtual_keyboard.display, global_handler); + + output = display_get_output(virtual_keyboard.display); + keyboard_create(output, &virtual_keyboard); + + display_run(virtual_keyboard.display); + + return 0; +} diff --git a/clients/matrix3.xpm b/clients/matrix3.xpm new file mode 100644 index 00000000..ef42f81d --- /dev/null +++ b/clients/matrix3.xpm @@ -0,0 +1,692 @@ +/* XPM */ +static char * matrix3_xpm[] = { +"512 598 91 1", +" c None", +". c #020202", +"+ c #020602", +"@ c #020A02", +"# c #061606", +"$ c #071E07", +"% c #061206", +"& c #020E02", +"* c #061A06", +"= c #0A220A", +"- c #0B310E", +"; c #0E3E12", +"> c #0A260A", +", c #0E2A0E", +"' c #134A16", +") c #0E5A0E", +"! c #104616", +"~ c #124E1A", +"{ c #166A12", +"] c #127212", +"^ c #1E921A", +"/ c #168A16", +"( c #1A5E2A", +"_ c #165626", +": c #227232", +"< c #3A965A", +"[ c #268A3E", +"} c #227A37", +"| c #1E662F", +"1 c #1E6A32", +"2 c #227E3A", +"3 c #16320E", +"4 c #36822A", +"5 c #4A9E72", +"6 c #4AAA6E", +"7 c #42862A", +"8 c #4DAE7B", +"9 c #1A3612", +"0 c #224616", +"a c #264E16", +"b c #32621E", +"c c #1A3E12", +"d c #265A1A", +"e c #367622", +"f c #3E7626", +"g c #5A8636", +"h c #4A962E", +"i c #669A3E", +"j c #82AE52", +"k c #3E9222", +"l c #4AA232", +"m c #66AA3E", +"n c #6ABA3E", +"o c #82BE4A", +"p c #76AA4A", +"q c #96BE5E", +"r c #1A9E1A", +"s c #22BE22", +"t c #2AD62A", +"u c #6AEE8A", +"v c #4AEE4A", +"w c #50CE94", +"x c #26CB26", +"y c #26D226", +"z c #1EAE1E", +"A c #2ADE2A", +"B c #2AE62A", +"C c #2EEE2E", +"D c #56F24A", +"E c #66F65E", +"F c #127E12", +"G c #42EE42", +"H c #3AEE3A", +"I c #2B9F4D", +"J c #3EBA6E", +"K c #2AAA4E", +"L c #56F262", +"M c #66C29E", +"N c #68D6B2", +"O c #52BE8A", +"P c #3CAE67", +"Q c #2A9646", +"R c #6AC6AA", +"S c #6ACAA2", +"T c #C2D672", +"U c #B2D26A", +"V c #4EAA2E", +"W c #9ECE5A", +"X c #5EB632", +"Y c #62C299", +"Z c #8ACA4A", +"..............................................................................................................++@@@@@@+++....................+@@@@@@@+........................................................................................................................++@@@@@@+....................+@@@@@@@+................................................................................................................................................................................................+@@@+.......", +".............................................................................................................+@##$$$$$#%@+..................+&##$$$$#&+......................................................................................................................+&#$$$$$#%+..................+&#$$$$$#&+..............................................................................................................................................................................................+&#$#%+......", +"............................................+@&%#***%&@+...................+@@@@@@@@@+....................@##=--;;;;;;;->$%@...............%#>-;;;;;-,*&+...................+&%&&@+............................................................@&@@+.......................@%*--')))!;-=%@+..............%*-;;!!!!;-*&+.....................+&%###%@+............................................................................................................................................................@#$-;;;-=*@+...", +"............................................%**==,-->=*%@.................@%*$$$$$$$*##%&+................%$--;~'))){))!;-=*&.............&#--;)){)~!--=#@...............+&###$$**%@+........................................................@%**$*##&@....................&=,;']^^/])';,*@.............&$-;)){//]);-=#@+...................@#*==>=*#%@.........................................................................................................................................................&*>;!~))!->*@...", +"...........................................+%**===,,>**%%+..............+@&%************%@+..............@#*,;!~~))())_';-=*@............+%*=-;~({_~;-=**%+.............+&%*$**$**%@+........................................................&&#*$*#$%%+...................@*=-!:<<[_~!-,#@.............@=-;'(}[<}_-=*#@+..................@%#**>>>*%%&+.......................................................................................................................................................+%$=-!)))!-,$@...", +"..........................................+&*$>,-;;-->$$%+..............@&$$$$>$$$$$>$$*#%&@.............@#>-;'((|(||1(~!--$%+..........+&**,-;_:}2(~;->$#&@............@%*$=>>=>$$#%+......................................................+&$*>=>=**%@..................@&*>3_456<:(~;,*@............+&=;!(:786<(;,$*%+.................@&#*>---,>$##@+......................................................................................................................................................@%*-;_|1|(;-*&...", +"..........................................@*=,90abbba3,*$&+............@%#*,333333333393>$#@+............&*,cdbbeeff4febd0;=*+.........+@%>-cabfghifbdc9=$#&+.........+&%$,39c09c3,=$@.......................++@@@++.......................+&#,39c993=*%+................+&*=3;bijjifbac,*@............@&=;adfijjifc9>*#@................+%*>,9abdac3*$&+......................................................................................................................................................&*>9aefggfa;=%...", +".........................................&*=;abgijjigbc9=#@...........@%*,c0db{b_0_db:bdc3=$&+..........&%=9de47kkklllk4ebac=#........+%$>c'be7mnonphebd03=*&+.......+&%=3cd:eeee(a3=*@....................+&&*=*$*##%@+..................+%*30d:1e(d09=%+..............+&$>9cdfmqom7bd09*&............@*>9db4ioqpgbac3=%+..............@#*>;afgmiifac,*%+...................+@&%#&@+........................................................................................................................+&*,;abipnpif03#+..", +"........................................&$-~]rstuuuvws/{;-*+........+&#-;)]rsxyszrrssysz/{';$&........+%=-;{/yABCCBCACCtyz^];,+......+%,;!{/sABDvuEvBAsrF{~-$&......@#=-;)/zxBBAAs^{!-*&.................@%$,-;;;!;;;-,*&+...............@*-;)/zytAsr/{;$%+............+%>;~F^sxAvGAs/]);>*+...........@*-~F^sxGvHAsrF);>%+...........@%$-;~]IxDuvDJ/{';>$%@...............&%$>----,$&+......................................................................................................................&=-~]^sADEDtKF',@..", +"........................................&=;~FIyHDEvDAsr]~-$&........+#,;)]/zsABxszssAAysrF);-#+.......@#--!]ryBCBCBCCCBBxsrF~,%.....@#>-!)]ryAHGEuELHBxzrF);-#.....@#*-;'{/yBACBByr]!;,*&...............@#=--!!)))))~!;-=%+.............+%=-!]rsBBBtz/];,*@............&=-;]^zxABCByrF);-=%@...........+#=;)FrsACBBysrF~-=#@.........&#>-;!{/KyDDDGAz])';-,=&.............&%=>;;!;;->=%@.....................................................................................................................&=;~]rJAGLDGs])>&..", +"........................................&#=-~][6MNNOP[{!->#&........+%*-;)(F^rI^/FF/IrQ/])'-=%........+&>,;~]^rIzKzKsKzr/F(~;=@.....&*>,;!)][IKOwNwOPKQF|~;,*&.....@#$>-;~]/rrzKr/F)->*%@..............+&*=--''~___~';,=*%@.............+&*,-']/rKr/F_!-*%@............@*=-!{2QIIIQ/(;->*&+.............&%=-!(}^IIr/[]);>$&@.........&*>,-;!(1-a|75RNNR5}('-*#@........@&$-!__:[55<}1}}5554|~;-=%+.......&#*,-_1[QQQIIIIIQ[}1('-*@....+&$=-!(:}[QI68OOO66<}(_;-=&.....+%*=;;_|[[QQQ[2|~!->#&.............+@%$-;~(:f774e1_;,$%@.............@#$,;_:<865[1_;,$&@............@#>-~|}}[QQ[|~;>*%@...............&**-'(}[Q[[}|_;=*%@........+&*,;;c~(e#@+...................................................................................................................+&$>3~|}@....@#>-abe7khhlmVmmjqqjgedc9>&.....@*>;adbe47k4k77ebac,*%+...........+&#*,9a:gijqqqjgea3=#@.............@*=;0bgpqUqifdc,$&+............@*,cde7kk777ba3>#&+...............@**,0de4kh7hfb0,$%@........&*,;abbbefiqUUqi4bbbba;,#............@&$3afjjjifc,*&+...................................................................................................................@%=,;df7hhh4fd3*@...", +"........................................%*9c:kiqTTTWjl7bc>$&........&$>0d:4ijWqoikhmqTqp7eb0,#+......&*=9cde4hlVVVVmVXVVk72b',&...+&*3a14klVVVVVVVXqWWoi7bdc>#+...+&$3cde4k*&+..........&*=3;d:75jqTTWomk:03=%+...........+&*30b4iqqUWpgba3*%+...........+%=30e[llVVlke_9=*&+...............+#=3cb4kllVlked;,$@.......@%=3d1e47hlpWUUWjhkk44ed9$@..........+%$,cbgqUWqgbc,$&...................................................................................................................&*,9dehllllheac=%...", +".......................................+$-~]zxCDuuEEvAyr{;,*+.......*-!{rsxADEEGABBGuEuGsz/{;=@....+%$;~]/zsAACGHvGvGGHCCAyz/~$...%>;~FzAABCCCBCCCCGvDHtsz/{;$@...&$-~]rzxBCCBCCCCBAs/{;-$%.........@*-')FrstCGLuDvGHtzF~->&...........&=-~FzyBGDEGAs/{;,*@...........&=-~FztACBABxr]'-=@................+*-'{^stBCBCBs/);,*+.....@#,;)/zytBHHDEEEEvvBBtyz^{-#..........@$-;{IxDEEDyI{!-*@.................................................................................................................@$-'{rxAGCAtsr]~-%...", +".......................................@#;~]ryADEEEEvBs^]!-#@......+%-;]^zyBHLLvCAAHvuDHsz/{!*+.....#-;{/zsACCBGLLLLLLGCCBBs/)=+.+*,;~FstCCCBCBtyxytBAAysz/{;*+..+%=-~]rstAABACBBBCtxr]'->*&........@=-)F^syBCCAAxyAAAzF~;=%..........+%-;)FsBACCBAs/]~->#+...........@=;)FzACCCBBs/{;,#+.................@$;)FsABCCCAs/{!-*&.....+$-;)/syBHLLEEuEuELGCBxs/);*.........+#,;~{IALLLDyzF);=%+...................................................+...........................................................@#>;~]IAGvHysz/{;-*+..", +".......................................+@*-;12IOSNSS8I});,*@........@*,;{F2IPwwPI[QIOwO<2|);,%......&=,!{F^IKKJwwNuNwwwJsKr}_;%...@*>;~FIKzKrI^/2F/[QQ^2]|);=&....&*>-!(]/^IKrKrKrrr^]'-=*%@........@*=;)]}^IKIQ2}}/[/]'-**@..........+@*=;(/QIQQ[[]);-*#+............+#>-~2PJwJK[]~;=#@...................&$-!{/IJJwP[(;,=#@......&$,;)F[QPwNwNuNNuNwJI^F);,#.........&**,;~:5wwNO<1_;,$%+............................................+@+++@@&@@@@@@@@+++................................................@#$-;):5wJP[F_!->#+...", +"........................................&*=;_2g8RNNR6[:(;=*&........@#=-~(1[5OO6[2}<6M6<|_;-=@......@*,;_1}[QKKOwNNNNSw6KQ[:_-#...@#*-':[IIIQ[[21::}22}:__!->&....+&*,;_(12[[QQQQQQ[2|~->$#@........+%>9'(}}QQQ}:1111|~;>$%&...........@#>-'|[2}}:(~;,*#+.............@%=-_26OYO<:_;,$%@...................@*,3~|[8YS6<(->$&+......@%=-'(14KONNNSNNNSO5[}(~-*&........@&#>,3_|*$%@...........................................&%###%#%#%#####%##%@...............................................&#=,9_1#@........@#=3adb4impifffgpjigdc9>$&.....+&*-;d:4kimpoqWUUUWoplkkea-%...@$*3c:7hhhhhg7feef4febbac3=%....+&*,;a(be7k7h4h7<44ba;>=$%+.......@#>9abe4hik7ebbbbba-=$%%+..........@&*-cde4ebddc->%@+.............@#=9dgjWWjiea3=*&....................+&=-cbgqqUqgb;,$&+......@%*,cd:fijqUTTTTUqji4eda-*&......+@%**>3caegqWWjiba;9==**@........................................+@%$>>>=>=========***%@.............................................+&$>90dfippgba;3*&+....", +".......................................+%*30|7ijTTTUpl4d0=*&........&*>c_be4hiig4ee7iiifdc3=%@......&=3abfhlXqZZZoZWUUZnXVk4d3#...@%$3aehVVlnnnVllhkkk4eba09=%.....@*>3ad:4khllIlVllk7eda9=*&+......&*>0db4hmnmh4}ee:(ac3=$#&.........+@#>,0debdaa93,*#@..............&*>9dhoWWZifd0,$@.....................%,cabgqUWqifa3=#+......@#$,cab4hmoqTTTUZnmk4bdc,*@.....+#*=,c!a(b7mqUUqi7bdcc33=#+.....................................@%#=,333;0cc0;0c;cc;c9,>*%...........................................@%$,;de4hlnmhedc,*%.....", +"........................................#-']rxBLuEuEvts/);=%........&$3)F/rzssszrrzsyxs/{!-,#+.....+#-~]rxAGvEEGHHvLEEEGCCts^)$...&$-;{rxCCHvvDGBBCCBAysr/{'-=+....@#,;~F^sACCCCCCACCtyz/]',#@.....+*,;{/zsAGDvtyszrr^F{~;-,=@.........@#=;)F/^])';;-*&..............+*-;(^JvEELtK/{;>#....................+#-~2IyEEEEtK]~3=@......+*-;~]^zxACLEEEELGByz/]~;=&....@*--~)F^rzsxGEEEEHssr^F])!,#....................................@*-;~){]{/^^////////FF]{);=@.........................................@*>;)FzsxABCAs/]_;$%.....", +".......................................+*-;_^stDEEELGAs/)-=&.........%,'{F^/rrr/FF/rzz^]);-*@+.....@*-~{rsAAGLLvCBCHLLLGCCAsF'*+..@#,;)/stBBHvGHCCCCCCBsz/{);>%....@#=-!{/stCCCCCACBBAxz/]!-$&....+@*>;{/zsAvLDBxsszr//F]~!-=#.........+&=-!]]])!;-$%++..............@*-;)^xGEEuAs/);-&....................@$-'{IxGEEDAz]~-#+.......#=;~]^zyBHvLEuLGCBxz^]~;$@....%--!{/IzsJyAvEuELGAtyssr/);*...................................+#-;']/rKzzzsssKszszszzK/])-#.........................................%*-;)/sABCCBys/)!-$+.....", +"........................................+*-;~:[PSNNOP}|!-=#@.........+&$-;;;~!!;;;;!'~'-,$#@.......@%>-'(][QPwwJKKKJwwOIQ}F{;>@....+*>-~{F2[QPPKKKKKJJKI/})!-*@....+&*=,;)FQrKKKzKzrr/F{~;>*%+....++%*=;~:2-;([OwNO<:~;=*@....................+%>-!(-;_]QPOJ86PPJJPQ}{);,#.....&$-;|[68OJOYSNNNNNOOO665[_-*...................................+&=-!1<588866666O6666668<}_,%.........................................&*=;~]/IrIQQ2{!-=*@......", +"........................................+&*=;_:==,>====>,,*$%&+........@%*>;~(|[5OO6<[[-ab5RRRRRNSNNSNNNRNNRM5b9#....................................#=3af6MMNMNNRNNMNRNNNRRMg(-#.........................................&$,;_:[IIQ2:(;-$#&+......", +"........................................+&*>30biqUUjgbc9=*@.............@&*##**$#$*$#$#%@..........@%$=9cdbgpqqmg7giqqjgbbac,%+.....%*>>30dbef7hhlmmoZoik7e09#.....+@#$=90:7hhVmmlkh4edac-=#@......+&$=cabfiqUUjmll74eeeeb_c=#...............+++++...................+&*,9agpWWqgb;,>#@...................+@#*,;dgjWWjib;,$&.......+@#*,cabijqpg7fgjqjibdac=&+...+%>3agjqTTTTTTTTTTTTTTTTUifc#...................................+#>;dgjTTTTTTTTTTTTTTTTTUibc&........................................+#=30b4hhkkeba-=#@@.......", +".........................................@$=3abiqUUqib03>#&..............+@@@&&&%&&&%&&@...........&*,9abe7kpqWjlkipqWoi7e|a3$+....+&*=3c0|e47klVVXXZZoXVk7ea>@.....&*,3cdeklVmXXmVlkke|dc3=%......@#=90d:7iqWWZonXlhk7kk71a9#+......................................+%$-0bgjWUWifa9>*&+...................%*,90eioWUqibc3*%........@#>-0dfioZp57kipWqi4bac>*+...@#>!cgjTTTTTTTTTTTTTTTTTTjf0*...............+@%%%%%@+...........+%30agqTTTTTTTTTTTTTTTTTTjb0#.......................................@#$9a14kVVVk4(c3$&@........", +"........................................+%=;~FIADEDDyQ);-*@..................+@@@@@@@@+...........&=-!{^xtvDEEEEvGHDuEDHtyz^)-%...+&=-;)F^sxxxtACCCHGLGHCBAs/!#....@$,~{FzsABCHCHCCCCtxszF);$+....+#,;)FrsxHvvvDvvDGGCHABts/'>@.....................................+&*-!{ryuEEEtKF)'-$@..................+$-;)FrtLEELAK{!-$@......+%=3!{^ztvvHxssyHvGAs/]~3>@...&=30|.............+&*>--;-->$%@+........+#,~e5wuuuuuuuuuuEuuuuuuSX[0$...............+&##**%&+..............+$-!{^sAACCCAs/)!-*@........", +".........................................&--'{rwvLLGy/{;>#+......................+...............+%>;)FKGLuEEuEuELEEEEEGCAyr);%...+*=-~]rzxAAyAAytBACCCBCCBs/)$....&>;{FrsxBBCBGBCCCBCBtsr]!,%....&*,;)/syBACHHvLLLELLHCBBs/),@......................................&=-;)[stvvDAs^{!-=#+.................+*-!)FIyGLLDxr{'-$&.......&$-~{/rsyAyzzrzxAxsr/{~-=&...+#,;~]rsXxwvLEEEEEEGtxxss^]!*+...........+#*,;!~~!;--=*@.........&>;~FrVsXsXxxXxXxxxXsssz/{;#..............@#*=>-,-=#@.............&$;)]zAACCBByzF);,#@........", +".........................................+%*-;|QJYw6[(;>#@........................................&*>;(<8NNNSNNNNNNNNNwJKI/|;=&...+%$=-~]//^^/////^[QIKKKKrF'-#....+*=;~{]F/QIJJKKKzKzIr/|!-*@....@#=-'1/QIIKKJJwwwNNNwKKr/(;*.......................................+&$,;_25PJPI[]~-,$&+.................+&*,;(2;~(:]11_~_)|2](~;->#@.....+%=,-c~(||e[6wNSR542:b(a!->&...........+&#$>-!~)~'-,*#&..........@$>3!!))()d|((|(dd)0_!';-%+.............@&**>=,==*$%+............&*>;~F^IrrQ/F(;-*#@.........", +"..........................................@#*-cb<55<1;,*%+........................................+%*-0:58OMRSRRRwMMRO85[}|~-=+...+&*>;_1}[Q<<<[[}[[Q5KIIQ[(!,&....+&*-;!_(|}[QKIIQQQQQ[}|;,$@....@#$-'_}[QQQQIPJYORNSO5[2:~-#+......................................+&%*,!([#&@.................@#$-0(1[--c_(:5RNSO<:d~!;->=%+...........@&#=-'(|1|_!->*@..........+%*=>,3,-3c;c-33----,,>=*%+............+&$=,-;;;-,*#&...........+&*=-!|[QQ}}}(~-$#@..........", +"..........................................@#*>cabebb0->*&.........................................+%*,;dfg5i5mm8mm8m5ig7:da9=#+...@%*,cd47>,,-,,==>,,3,=*$#%+........+&**>,39afiqUUjiba09-=$%+...........+&*$,9~bee:bd;,*%+..........+&***>=>=>=,=>===>=>*$*#@.............@%>ccadbda;,$%+.........+&#>=cae4hhfebd0,*%+..........", +"..........................................+%$,-0dbbd93=$&..........................................@=,3df7hlVVXXXVmVlVh4ba;3=%....+*>30ekVlXoZWommXnZZZmVh4b0,@.....@&**-99_b4hVVVkkkcd4lVVVllhhhlmoWUol7f1d9$+.......................................@#$9'b4klllk7eac=$&................+%=3017klllk7:a9>#@...........&&#*$>$*$$#$*=$$*%&+..........+&#*,90dfiqUUqifd;3=$&@............@*$-9d:kkkk4bac>$&...........@&#$*$*$*$***$*$#*##&@@+............+#=;abe474ed9=*%+........+#>30~ehmpl7eda9=$&+..........", +".........................................+@*,;_{/^^2{'3,#.........................................+#=c_}zxtAHHCCCCCCCCts^F';>#....+$-'{^yCCCvLvvvGGvGvGBtxrF',%.....+&*>;')]^stCACAyAACBAs/)3%...+#-;{/sCCCBCBCBBCCvLEDHysz^];&.......................................@#,'{^stAACCAs/{;-=&..............+%>;~FzAACCBts^]!-$&...........@&##$>,=*#&##$=*%&+............@#$3!|[KwEEEvAKF);->%+...........+$-;)]ryBHHBAs/{;-#+...........+@%%#%##*=#%#&&&&#%&..............+%-;)FrsABAsr{!-$&.......&=-~]/rsALGtJ/F);-*%...........", +"..........................................@*-;'FQzr/{!;,%+.........................................%,;'FryACBHBCCCBCBAyzF)'-=&+....%=;)^sACCBHGLLLLLGCAtzrF)',@.......@#-;)]^sAACBAABCABAz/);*+..@#>;)/stBCCCCCCCBGGLLGHCAsr{;%........................................%=;)FzyBCACAs/{!-=*&.............&=-;)/zACAABAz/);>#&.............+@&%%%&&@.@%%%+...............@*-;)FztLELDxr{~-=#@............&$;~]^sAvLvHts/);,=&..............+.+.+@+++..+.+++...............+*-!{/zxACBAzF~;=%.......#=;~FrzxtGvAz/{~;$%............", +"..........................................+&*-;|[I<[(-=*@..........................................&*>-'{F[IKPKKKKzKrQ/]'->$%+.....@%=-)]F2/QIKOwNNwPQ[])';=*&.........+#=-!)F^KzKrrrrKr/F'->@....+#=;)F^KKKJJwwOwOOOOJJO6P2~-&.........................................&*-')F/rIzI/{~;>#@@............+@#=-!(FrKz^//]'->*@...................+.........................+#>-!1-~1[6wNSJI2(;,$&+..............................................@*-!{FQKJJP[_->*@.......&*>;~:2QIPI<:~;,>%+............", +"...........................................@#=c15O8<|;>#@..........................................+%*-;_|2}QIIKIIIQ[}|_;-*&+.......@$=-;~_((:[8RNN6<:(!-=**%@..........+#*=;(2QIIIQQQ[[2(;-*@....+@*>;(}[<5YRMMNMMO6P6YRR62a,&.........................................+*=-'(:2QK<[1~;-*#+............+%*,;~|}<<<[:_~;,*&+..............................................@*>9_78YR840->$&+.............@#=-~1[56O852(!-*&+...............................................+#>;(2<8YR6ea-=%@.......@#=-_:2[[<[:(;,*&+.............", +"...........................................@#=0bijjib3$%@..........................................+%*>9abe47hlllhh<7ebd9>$&+.......+%*=-339cafiqqqpfd0->$&@............+%$*3_e47477744ebd3=$@....+&*=9a:47ijUUqTqqjiimjqqjg0-+..........................................&*,9cdfgjpieda;>#@............+%$>;dbgppi7ba;,$%@...............................................@#=3agiqjifc3*#@.............@%#=9af7giig7ba3>#&.................................................#=9aegjqqpfa,*%@.......@*=cdf77h77ba;>*&+.............", +"...........................................@#>9agggfa9*#&..........................................@#$3c(e4klVVVVlVVlkea9,*&........+@&%#*>>9abhmooifa3>*%&.............+%#>3cdee44ee:bbd03$%@.....@#>,0dbfggiijjjiih4ggjggb3=&..........................................@#=-caemoom7eda3*@............&#,cdb7loZp7ba9,*%+...............................................+%$,9dggggd0,$&+.............+&*-c|4ll5hhfd09=*@+................................................%=9cbggiggb0,$%+.......&$,0|7klllke_3=$@..............", +"...........................................&$30ab1bd09>#@..........................................+$-'{/zxtCCCCCCCBBtzF~-=#........@@@&#$--!{/xtHCxK]~-=#@.............+%$,;!)]F///F]()~'->#@.....@#>-!(]}[4hhhkhkQ^^Ik7fbd0,@..........................................@*,;!{rtvvtJz^])-%...........+&=;{/zsADLGK^{!->#@...............................................+%$30_|::(a;-$&+.............%=-'{rxCBBAxzF);-=@.................................................%>;~{:474bdc->#+......+%-!{rxAAAAs/{;-*@..............", +"............................................%$-;;'';--$&...........................................+&>;)/rsAACCCCCABByzF~-=@.............+#>;'FzAAxs^]~-=%................%*,;!~'))))~';;->#&+......@#,;;~))){){{]{F]{){(__;,*@..........................................+%=-'(^JGGAsr/{!>#+...........@*;)F^syBwAzF);-#&+................................................+*,-;!'~;;-$%+..............@*,;]rtABxsz^]!;>%+.................................................+#-;!))))~;--#+........*-;{rsyByszF);>#+..............", +".............................................+%*====%&+..............................................%=-~{]FQrKKrIrr^F|'-*&+..............@#=-!{//2]';,$#@.................+&%*=====,,=***@+..........@#$>,--------;-,-,-=,=#+............................................+%*>;~1241(~'-,%+.............@=-;!(14}:~;,#&+...................................................+&#*>=>*#&@.................&*=;)FFFF1)!;>*%+....................................................&#$>-,,==#&+.........&=,;)]FFF{);->*@...............", +"..............................................+&&%%&@+...............................................+#-;;~(|}}2}221(~!-**@................@**-'_(_!->#&+....................@&#*&%%&&%#&+.............+@&##*****==*=>***$#&+..............................................+@**-99'c-->$%&+.............+@*=,,-cc3-=#&+......................................................+&&#%&@+.................+@#>>;_(_!--,$%%+......................................................+&&&%##&@...........@#=-;_((_!-->%@+...............", +"................................................+..+.................................................+@#,-3cc_db_d_0!;-,*@+.................@#=-9;3-=$%+......................++++.+@@++.................++&%%%%%%#%%%&&@@@+................................................+%**>>,,=**%@+..............+@&#*$>>>**%%@..........................................................+@+....................@&*=3ccc9,>*#@+.........................................................+++@++............+%*39ca_cc,=*&.................", +"......................................................................................................+&**=,,3993,>>==>*%&..................+@%*>=>**%&+..................................................++@@@@@@+++@@.......................................................&***$*#%&+.................+%%#%#%%*%&@+.................................................................................+%*=-9c->=*%@+............................................................................+&#>3999c,,*#&.................", +"........................................................................................................&#*$$>>$#&&@%#*#@.....................+%***#&@+.......................................................................................................................+&%%#%&@+...................@@&@%&&@++....................................................................................@&*$>>>*%#&..............................................................................+@&#$>>==**%@+.................", +"............................................................................................................++++......+...........................+......................................................................................................................................................................................................................................................+.++++++..................................................................................+.+++++.+....................", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +".............++@@@++..........................................................++@@&@@++....................+@@&&&&&@@@@@+.......................+@@@@@++...................++@&&&%@@@@++........................+@@@@@++................++@@&&&&&&&&&@@++....................+@@&&@@@++....................+@@@@@@@@+...........................................................................................................................................................................................................", +"..........+&&#*====####@+.................+@%#######@+...................+@####==,=>=*##&+...............+&#*$===>===**%&@.....................@&**$*$*#%@+............+@###==>>,,,>>=*##%@+.................+@#****===###&@........+@###==>,,,,,=,,=>=*#&+...............+@#*====>$**&&@.................@%%**$>$=**#&++................................................................................................................................................................................++@@@@++...............", +".........@#=>-;!~~)!!;-,$*%@+..........@%*$>-;;;!;;;>$#&@..............+&#>-;;;)){)~!;;-,$%@+..........+@$>-;!){{))~!;;-,=#+.................+&*,-;;;;;--$#@.........@#$,-;!)){)))))))!;;-$#@..............+&$=-;;!!~))!;--=%+....+&$>;;!')){{{{))){)~!!;,=%@..........+%*$-;;!)))~';;->=#&+............+%$,-;;!~~~!;--$$%@+...........................................................................................................................................................................+@#$=>>,>$****&+.........", +".......&%#*-;!))]F]])~!;--=*@.........%#=-;;'))))()~';>*&@............@#*,-;'){]//F]{)'!--=*%@.......+&#=,-;'{F//^F]{)~;;->$%+..............@%=--!~))))';-=*&.......@#=;;~)]F///////F]{)';-=%............@%#=,;;!)){]]]]));-=&....@#>;!)]FFFF////F///]{)~!-=*&........@#$--;~)]F/F]{)~';->*%@..........+#*,;!))]]]]))!;--,*&.........................................................................................................................................................................@&#*=---;-;-;---=#&........", +"......+&#*$>;;~{]1]1(~;;,>**@+......+@%$*=-;!))(({{);,$%&+............@**=--~){]}/}]|)~;-,>$#@.......@&#*>-;!)]1F}F:{();-,=*#@..............&#$,;!~)~)~!;,*&@......+&$=-;')(F}:}11F:]|_)~;-*&...........+&#*>>-;!)(]F1]()~;-*&...+&$=-')({:F}F}F}F}F}]_)~;-*#@......+@&#*>-;!~(]1F1{)!;-,>$#&.........+&**>-!)({{]|)~!;-=>#%+.......................................................................................................................................................................+&&*$>,----;--->=>*#+.......", +"......@%**--;(_:}[}211_!-=>$#+......+&**,-;!((1}2}}(~3=#&+...........@#$>--!_|}}[[[}}2(~;-,=*@.......+%*>-;;_|}}[[[}}:(!;-,=*&.............@#*=-'_((111(!->$&+.....@%*>;!((}}[[}[[[[22:1(!->%+..........@%*$=-;'_(}}[[}21(!-=@....&*>;_(|}}[[[[}[}[[}}:1(!->#+......+&%*,-;;_|:}2}21(_';--=*&+........+%*>-;_(}}2}:11_'-,=*#@......................................................................................................................................................................++%#*=,;;;!!!!;;3-=$*#@......", +".....+&*>,cabff77hk77ebba9,=*@......&#>3;adbeggghigeb;,*%+..........+&*=9cabf7ghhhhhhg7bda;,>&......+&$=3cabf7g7h4hh4g7bda9,=&.............@$>-cbegggg7eb;,=&+....+&#=,;abfghhhhhh7k77eeba-=*+..........@*=3;0befgghhh77fba;>%....&*3cbef77k7hhhh7hh774ebd9=*@.....+&#>,;adbefgkh7k74fbba;3=*@........&$,cabff74k74febba09=*&........................................................................................+..................................................+@++.......................+@#$,3cdbbebeebbb0;,=*&+.....", +"....+&=-cabegmmmVVVVmpm7ebac>#.....+%=-'b47kmmoononm7d9>#&..........%*>cd:47lmnmVlVVXnml7eba,#+.....%*,0(e77lpmnVVVmXmmh7eb03$+...........+#=cabgijjjjji7b9>*&....@%$-0de7lpnXlVVlVVVlkk4b0,*&.........@#>9abgipppoonXVVk4b0,#+..+*,c{4hIhlVVVhVVVlVVVllkea9=&.....&#>3abe4hmmmmVVVmpml7eedc>%.......@#=9dgiimlVVVlVlkk7}d0,#+....................................................................................+@%%#%%@+.........................................+@&%*%#&@+.....................&#>90bgippjjppppi7bac,>#+....", +"...+%>;)FrzyHvvvHCHHHDGtysrF~-&....&=;)/sABHvEuEuEuvs^);,*+.......+@=-~]zytHGDvvHCHGvvvHBAsr{-%....+$-)FzyBBvDLDGCHGDDvHAys/)-%..........+#-;{^swEEuEEEDy^{;-%....#=-)]rstHDEDHCCCBCBCCAsr{!-%.......+&$-~{^syuuEvEvDGCCBy^]!=+.+%-']zxCCCCCCCCBCCCBCBCCts/);=+...@*-!]rsAAHGDGHCHHHvvGCtsr]!>.....+%*-'{rxDvGCCCCBCCCCBtz]',&.................................................................................@%*$>----->$*&.....................................&#*=---;-->*#@+.................+#-!{/zxDuuuuEuDuHtsrF);,#+...", +"...@#-;)/zstHDLvBCBCGLDCtxz/);#...+&=;)ryBCHDEEuEEEuA/{;,*@.......+#=;)/sACBGLLGCBCHDLvHCAyr];*....@*;'/sABHGLLvHCCvLLGCCBsr(;#..........@$;~]rstuEEEELDA/{!-#+..+#,;)/zsyHLLvGBCCCCCCAAxr{;-%.......@*,;)FKsHEEEuLLLGCCBxr]!$+..&>!]ryBCCCCCCCCCCCCCCBCBs/);>&..+%=-)FzyACGvLvHCCCvDLGCCAz/'=@....@#,;'FIAGvvCCBCCBCCBCBs/);*...............+@&&&@+............................+@%%&@+.....................+&##,--;!~~!!;->%..........@&&&&&&&&&&&&&&&&@........&#=--;!'!~!;-,=##&...............&$-!{^sADEEEuEuELEAsz/]~-=%...", +"...&*=-!(F[QPJwJJKKJJwOPI/F|;>#....+#=;)2/IPOSNSNSNOQ|!-*&+........&*=;)F^IKJwwJPKKJJwwPKI^]'-&....@%>-)F^IKJOwJPKPJwwOPKr^]!-&..........&*>-~:[6YNSNNwO<1;,=@....&*,;~][QPOwOPKKKKKzrr^F);,*&......+&*$-!)2Q6MNNNNwwJKI^F);,%...+&=;{FrIKQKKKKKKKKJJsKKr});>&....@*>;)F[rIJJwJJKKJJwwJKIrF(;*.....&#=-;(25wwJKKKKKzKsKKK/(!-#..............+@%###%&@..........................+@&&##%&+..................+@&%**=--;!!~'';->#.........+@&&%%%%%%##%%%%%%&+.......@%*,--!'~'!;-,=**#@+.............%*,-!(25OwNNNNNSwOP[2]);-*&...", +"..+&*>-'(}[QQ66;_1:[<88RNNS67(;-=&.........&#=-_}[Q<5665Q[[Q586PIQ[1~-%....@%*-'_}[Q<55<[[[%.........+&*>;~|2<6ORMRS8<(;=*&....@$=-__1}<6JPI[[2[[[[}|(!-=#@.......@#>-~:7<88OOO659~(}<66,-;!_{2ee1_c-$+.......+&%%###$$$$$$$$$$###%@......@#>-ab1e:1(~!-->**#&@+..........+&*>-!(1Q5O866868O8<[21(',*@...", +"...&*,;de7khhh77ebef4ghhhhhed9%.....@**3c~abffgjqUWjgbc-*&.........@$=-aehhhh77feebe77ihlhhea3%....@#*,0df474eeebeef7hllkh4ba,&.........@#=,0behhihimjZqgb9=*%....@*=3abf4hhik7eeeeee:bba;->*@.......%*,;agpjjmiig44febda93=#&....&*=3c0a_dddbbbee7hklkh4e03*@....@*=3de7hhlkh4fef47hhilkh4b;%....@#*-cdbehhl4eeebe4ipqZpiba9#...........+@%$*>,,>=$*#@......................@&$*==>,=$#&+..............+&#$==-9cabfgjjjpgd9,@......+@&$$>>======,=====**$*&.....@#,cgipjpg7bbacc,,=$%@+..........&*,;abf7hihgffghhlihh7e09>%...", +"...&*9a|kVVVlhk4eebe4khVVVlkbc$+....@#*=,90adffpqUWqgb03=%.........@*>3d4klhk4f1b|eekhllVll4b;#....+%$,cdef4eebee1e7hllVVVl4d3#........+&*,;aehlVVV6XoZqibc3>&+..+@$,cde7kllVlhk444444e|d!9=$@......%#30dfioZoV5l*@....@%$=,3c090!a(b14klVVVlkea9=%....%$30_7VVVVVhk4ee4khlVVVlke0>+...@*,0de47lVlk4e1ee7loWWqm7d9*+..........@*=33aadd09,=#@....................&*=39cadda93*#@...........+%*==3c'd(147hmoWZjifa9#.....@&#,3c0c0aadadadaaaa0c9,$&....&*3agqqZnXlk4e:bdcc,=$#@.........&=3ade4kVVh7fe4klVlVVl71a,*+..", +"..+$-'FrtCBCBBxszrrzstBBCBByz{,+.....%#--!_|/QsGLEuDyI{;-#+.......+&=-!{rsxszzrrrrzstACCCCCxr(>....+#>;)]//rzrzrrzsxAHHHCCAs^)=.......&*=-~]rsAHHBCBHvLDAK];-#+..+&,;)/zyAHGGGGGGHHHAAysrF);-%.....&>;)FQxGuLGGBCBAtxsz/F{!->%....+&*=--;;!'){F^rxtBCCABxr{'-*+..@$-;{rsCCBCCAtxsssxCACCCAyz];&...#-'{^zxtCCCtsszzssCvEEvts^(-.........+%$-~{/rxxsz/~;,*@.................@#>;~]/rsyys/);-*@........+%*--~)]/rsyyAACHvGGHsrF!*....@*-;)F/rrrzzzsszszzzzr/F{;$+...&=c1KAGGGCCBHtAAsrF{!;->#@......@#-~]^zstACtxsssACCCCBCAr]',@..", +"..+*;~]zyCCCBBysrrrrzyABCCCxr{-+......@#--!){^KtLLELxr)!-=&........&$-;'{F////^^//rsABCCCCAyr)=+...+@#-;)){]F/^/rzsAGvGCCABs/)=+......&=-!)/ztvvGCBBCGLLtz]~-=@...#=;)/zsACLELELLLLLGHAxz/{;-#....@#-']^zxHvLvBCBCCBAyzr/]';,#.....+@**>-;-;''{/rsACAABts^)!,#...+#-'{/sBCCCACAysssAACCCCCtz];%..+#-!]rzyABBBAszzrsstGLELHs/{=@........+*-!{/ztHLLtK]'-*#.................@$-;]/sxGGDAK]!-,%......+@%#-;!)FrsxBABCCCCAAysrF)-#...&*=-!]rsyAAyyAAABAAAAtAsz/)-%...@=-!{^zsyBCCLLLvAxrF)!;--=&+....@*-!]/rzyAAAsszsytBBCCCxzF'>&..", +"..+&=-!][KzKI^/]_)({]F^IKzK^|;*........+%*=-'(25SwwOQ|;-*%+........+@#$=--;;;;!'~){F^IzKKKr2(;*......@%*=,--;~)(|12IJwwJKK^F)-%.......&*>-!(2KOwJIQQIJwO<1!-=&....@*>;)][IPwwNNNNuNNwJIQF(!-*&....+&=;'{2[PwwJKKIrKrQ/2()!->*&.......+&&#$**>-;~{FrKzr^FF~;=$+....@*>;)FQrKKKrQ/FF/^rKzKIr^1!$+..+&*-!{F/rIKK^2]1112IOwwO52(;*..........%$-'{[#@..................%=-'|[POwwO4_-=*&.....+@&**=--!{F[rKzKzKKI^}}1_;>%+...+%*>-)F//rrrrrrrKrr^rrrQ/(;=@....&==-c(]}QKJwNNwJQ}{~;;,=$%+....+&#=;~)]FF//}]]2/^rKKKK^{~-#...", +"...&$-!([QIIQ[|(~';_(|}[IIQ2(;*+........@%*-9_1,,;;'(|}QQIIIQ1~-%......+&%#*==-;'_(11<6OOPIQ[|!-%......+&$,-'(|<6O8<}}[5O8<1'-*%+...@%*-;(1[6MNSNNSNNNY6Q[}(;-*&.....&*-'_|[@+....@%*-;|[[QQQQ[}1:12[QQQQ[}_;*....&*,-_(}}QIQ}:|((1:<6RSO<:~;*+........+@*=;(2--;!(1}QIIKIIQ[}((';>*@....@%$=-_:[QQQQQQQQQQQQQ[Q[}(!,%....+&*=-;~1}Q8YNNR8<}((~;-,=*%....+&#>=;!_(1||((((:2[QIIQ2(;>%+..", +"..+&*3cb7hkhk4ba!ccadb44hhk4d;#.........+&#=30biqWWjgdc,*@...........+@%%#*$=>,;aab17khlhh4d9=&.......+&&%*=,;abffgiqqomk7fd9=&......@%*39abfipomh77gpqjib09=&....@%*,;abeioqWUUUqWqopih7fa;=#....+%=3abb7pqqomikhkhk77fbd;,*@...........@&%$*3cde75h7b|a;=$@.....+#$3cb47khikiggeggiklkh7e03#....&*,;abe4hkh7bbbbbfijWWqifa3*.........+&*,cdfijZqpga9>$@.................+&*-0bfijZqpea3*&+.....@#*=9aadbf77hhhhhh7fbaa;9=#+....@#>-9df7,9ae7ijqqqpg4febba0,=#+....@&*>99aadbbb(bbe77khhk4dc=&...", +"..+*=9_e#+...........+@##$>>-ca|e4hmnnVlk4b0=&.......+@%$=-9dfgijjqWUZmlk4ba3%......&*-;be7klmmmh7klnZqiedc,#....@%$30db4hmMnppjoonZXopmib09#+...+#=c(e7hmZWZooonmVlmmpigd9,%..........+@&#*,c_b7mnmhfda3=*@.....@#>9~b4khmnooojjjjoomlk4ed9*+...@*>0d:4hmnnk7ee47hpZUTqpgdc*+........+%>3ab7hXnnm7d9=%&.................+%*3a14lnnnm7a9>$@.....&*>;de77hmpXmVVVkk41d0c9=*&+....@*=9deklVVVVVVVVVVVVVVVVk4d9#......@#$=900e7ipppmVVimiigedc>@.....@#=,9c0_d1ee4hlmnXVVh4bc3#...", +"..&=;~/ztCCCAAsr/F]/zstCBCBxzF,+.........&>-_:IAEEEvyQ{;-$@............+%*=-;~{^zyAAvvvAAszF);&........&*>-~]rsHuuuuEEvGtysrF'$....+#>-)]zyBACCCBBAAAGLDts/]!>@...&*,;)F/zxBGHtAxtHBGLLDDtKF',&...%-!{^sxCGDEEELLDGHHGDvvyK]!=+.........@&#>;~{/zxGvvtz/{;-=&.....&>;)]^zxtGuEuuuEEuEDGHxxz^(>+...+=;{/zxAvDDHysyyBBGEEEuDs^(-.........&#-']rsACHHyr]'-=%+................@*-!]rsABHHxK]~-=@....+*;~FzxCAHGvDHBCBxs^F{!;;-$@.....&,;)/ztCCCCBCCCBCBCCCBCCBs^(>......@%>,;~{FrssAAHHHHDvDHxz/~$.....+%=-;!)]/zsxttGDDGHCts/)-*+..", +"..@=;'FzACCCCtsrF]]/zsABCCBAr{,+.........&=;)FKxLLELyr)!-=%.............&*>-~)/rxABHGvvAsz/]'>&.......+@*,;~]rJxuuuuELvAAxsrF)=+...&$-')/sACCCBCCAAtBGLLHsr]);%...@*=;!)F^stAxszsyxCCGLLDGzF)-#...&-;{rsAAGLELLvvGHCHvLLEtz]'>+.........+%>-;)F/syHGGyz]);-#+....+%>;)FzsABHGvDLuDLLLGHAyyz^{-+...+%;)FzsAHGGHtAABCHvEEuuGs^),@........+*>;)FzstAts/)!-*&+................+%=;)/zsAAxs/);-#@....+=;)FsACCHLLLGCBBsr/]~';-=#+....+#,;)FsAACBCCBCCCCCCBCCCCBxr)=+.....+%*=-;']/rssxACCGLLLGAs/)=+.....+%>-;)]/zxBCCGGGACAyr]'-*...", +"..+%>-']QKKKr[F|_;'_]/QzKKK/:!*..........@%=-!:5wNNOQ|'->#@.............@%=,;~:[6OOJ8P<[1)'-,#........+@&#>-;_:4h5lPPJPKIQ/})-%....+#>-!:QJwOJJzKrIIKOww6[})!-&....@&$=-;'){]:|(1]F[IJwww6}(;,&..+@=>;)F[IPwwO6PPIQIKOwww5[_;*...........@%>-!(:}QPPI}(;-=*&......@*,;)}^IIKPJ66P6P6JJPKr^/|;$.....@=-')12IPJOJJJJPJJwNNR5}(;*.........+@%>-;~){{{)!->#@+...................&=-;~){]{)!-=#@.....+%-;)/IKKJwwwPK^/F|';-=$%&+......@*=;)F^IrKKKKKzKKzKKKKzIr2);%.......+&%$>-;_(]2/QKJOwNwOI2{;%.......+%*=-'([IJJJ6PPQ[F{);,*&...", +"...&*-'|[QII[}1_;-;__:}QIIQ2(;*+.........@$*-0(%@.........+%#*==9'_b124<%...@%>,_(}[6YM6<[}[22<6YSY51~;$+..........+%*-'(:}[Q<[1!-=#&+......@#>;_1[QQ$#&+....................@&*=------>*#@+......&=;~1QIKPOYO6[}}|~;;==*%@.......@#>-!(2[Q[QQQIQIIQQQQQQQ[:~-#........+&#*=-;'~|:[IPOSNS8<:_-%........@%#=-_28YRO6<}|(~;,>*&+...", +"..+%*,cd4hhlk4bbaccad:77hlh4b;#..........@$=30bgqWWjgd;,$%@............@#*=9abfiqTUqigba;,*$%@.......+&%*$$>=,90abbe7hhllkhea-#.....&$-cbiqUqomhhhhlmjqqpkfba9%....+@%*$=>,,,,-39aabfiqWqp7d0,&..+&$>90bfgmqqjffbfbf7pqWqpfd9=@..........@#*3abe77h47bc9$*&......+@*=3dekhlhhhhefff7hhlllk7bc$+....@&#$-cdfhiqTTTqpmnqWWqifa;#..........++&%*$>>==**#&+.....................+@%*$>>=*=$$%@......+&=cae4lVnoqomg4fbbc;,$#&@.......@*,cabf7khhhhhhhhhhkhhhk4ea3#.......+@&$*=3cadbe7hmoWTUjiea3%........+%$>3agjUUqi7bdcc,=*&+....", +"..+#=9~ekVVVlk4:dd(bb4kVVVVk|0*+.........@*>3aeiqWWqiba9=$&+..........+#*-c_b7ijqTUqm71dc3>*&@.......+%*>=,3-3c0dbe4kVVVVVl7b9%....+#>caepqUWZXXVVVVXZWWolh7b0*....+%*>,,3--3,-90_bbhpZWWpgba,%...&*-0(e4lpWqjif4eefhpZWZpgb03&..........&*30(47khllk:a3=#@.......%*30(7VVXVVVhk444klVVlVVkea,@....+&#=,cd4kmqTTTZnXXZWWZi7d9*+..........+@%#*=,>=>$#&@.....................+&%#$==>>$*%@+.......#>cd4hVVXZZoXlh74:ba;3>*%+......&*3cbeQllllVVVVVVVVVlVVlh4d9%.......+&*=,90dbe44hVXoWUWoi4d9#........@#*,9bgqWWZp7edc-,$%@.....", +"..&=;)FztCBCAAszr/^rzxACBCBtz],+........+#-;(FKtEEELtK/);=#&.........+%-!)]rstGLLELGtsrF);->#@......%=-;)){))~)]//zyACBCCBBs^{=...+%=;{^KtEELvCCBCCCGLEEvHByz]-...+%>;!)({~~~'!~{/rzyGEEEvyrF!*...%-;)^sxAvEvvtsssssyGEEEHsr];%.........@$-~FzxACCCAyr]~;=#......+*-~]ryCBCCCBAyssstCCCCCCBzF;&....@%$-;)/zyAGDEEEGHvLEELHK^(>+..........+&#>,-;---=$*@....................+&#$=,---;-,=@........#;)FzxABHGvGGHHBAyzr]{!;>*&.....%,;)/stACCCBCBCCBCCCCBCCBsr(>......+*=-')F/zytHAHBHGvDGGxzF!*........@=-;(^yGvDDCsrF~;-=%+.....", +"..&>-~FzyCCCCByzr^^rzytCCCCyz{,+.......@#=-!{/sALLEGAs/);->*&.......@#-!)F/KAGvGGHHBys^]);-,%+.....+#>;)]//^]{{]/^zsxBCBCCAy/)=+..+#,!{^sABHHCCCCCCCGLELGCBtsF-@..%>-!{F//F))~~){/rKxGLLLDxz]'*..+%>!)/zyBHLvGyszzssyGLLuGyr];#.........&>-)FzABAACBxr]'-#@.......*-!{rxBCCCCBAxsssyBCCBCCtz];&....+%#>-!{^zsAtHACBCCGLLDts/)=+..........+%**>--;--,*#%+....................&#*>---;---=%........@=;)FrzsxACCvGLvHBBsrF{~;-=%....%=;)/zACCCACCCCCCCCCCCCCBx^(=+....&#=;;)]ryAGLLGHCCBBtssr/)-%........+%-;)}sAHHAxs/]~->%+......", +"..+#=-~]QKKKK/[F|{(]F2/IKKK/]!*......+@&%*,-!_25wNNO<1(;-=*%@......@&%=;~(][8wwJPII[2]~;-,**@......@#*-!):]|(~'))(]F/rIzKKr[);#....@#,;_][IKKJKJJJJsJwwwJPK/F)=...&*>;~{]|(~'';!'(1:Q6SNS8[|!-#...+#>;):2IPwYPQ}2F:}Q6wNw6[|'=+.........&*=;)F^rKKrQF(;=$&+.......&=-;(^IzKKrr//FFFF/IzKKKr:'=......+&$>,;~1:22[[Q^QPwwww5}~-%...........+&$**=>=,>$*%&+....................@%#$>>,>>=$$%+........%=-;!)(]2QIOwwwwKK^])~;-,*&....&$=-']^zKzKzKzKKKKKKKzKrI/);%....+%*=,-!(FQPONNNJPI[2]_~;;-*+........+@*,;_:QIII2{!;,*%+.......", +"..+@>,;(}QIIQ[}:1((_:22QQIQ2_-#+......@%$>,-0_15YSS8<1_;->$*&+.....+&$>-~(|}5YR6>$&+.....@#>-c(|:1(~_~__(1:[QQKIQ2~-%....+%*,c_:[QIIQKIPKPPOSROI[[1'$+..@*=-__|:1(~~~___(1[8YNR8}(;=@....&=-!(145YY5}1(|(:48YSY5e_;*+........+@*>3!|[[QQ[2:~->%@........%*,;_}QIQIQ[}||||}2QQIIQ[1;*+.....+@**=-;_((||:2}[56SS87(;=#...........+%#>>-,-===$%@+....................@%**=>--->**%@........+&#>,-;~(:<6wwwJPQ[:(~';,>%....+%$-'(}[QQQQIIIQQQQQQQQQ[{~-#....+%*>-;'_:[5OSNS8<2:_!c-=$*&..........@*$-!|}[Q2|~->*%+........", +"..+@*,;de4hhhh7feb:bf77hkh7ea,%.....+@#**,;cabfiqTUqiebac3>=#@.....@%*3cdffgjqUjih74fbba;3==%@.....@*=9abf7ff1bdbdbf4ghhlkkea-%.....@#=9abe77khlllmmoqWqjihfb0>...@*=3_beeeebbbdbbffgjqWqpfdc>@....@*,;abgiqqigebebfgpqWqmed9*..........&*,;af75hhkfb'-$%@........&*=caekhlhhg7febf47hhllh7bc*......+&*>=3cdbeeef77gmjZqogd9=%..........+&*=-;0a0!c3>$%@...................+&*>,9;a~c0,=*%+........+&#*>39ab7iqqopmkh7fbd0;,*+...@%*,;a:f44747777k4k74774fba,%...+@$,cabbf7kmjqqqp7fd03-,*#&+..........@*$9cb77k4ba3>#&+........", +"...@*>9aekhVVmmikkkhlmmVlhkb03#.....&#*-0ad:4gijUTTqph7e1dac,#+....&*,0bgimjqWWomVlkkk4ebdac>#+...+#=-aeimmmikk4[4khmXXXVVh4d3%.....+%>-0dbe7khllXVXZZUWoVlk4a$+..&$-'b4khilihk7kkimpnZoqifa9=@....@*>cad4pqWjlkhhklmoUWWpgb0=+........+%*,cbgmXnmh4bc3=%@........&*-c(4hVVXXmmh,0a1477kkhlmmnonomgbc>&..........+%>9abeggged9=$%@.................+%*,;_b7gigedc>%&.........@%#$>90df4iimVVVVmmiifbc3#...+#*=3adbe4e4e44e4e4e4eeeba;>&...+#>0bgiiihlVmjjihfba3,=*#&+..........+&*=3_eklh7:a;=*#+........", +"...&*-!]^sxBGGvGBBBHGvvHAxs/{;*.....#,;)/zsyBBHvuEuEvHABxsr]~,&...&$-~2swDLLEEEGHCCCBCAtsz^]!,&...%>;)/sHDDGHABCCABHvDvGCCAzF!*......&=;!)]/rzxBBBCBvLELGCCAs]*..+#-!]zxACHvGHHBBHGDDGGHHAI]'-&....%=-;_FItEELvCACACGLEEvtJ/{-&.........*-!{rxDDvvyz/);-#@........%>;)/sABHHLGvCCACHvDGHCAxr{,@....+#,;)]rsAABACBHvGDGBHAz2)9$.........+#-!{rsAvDvwV]!-=%................+#>;~FzxAvDDwz1;-#@........+#*=-;~|:/rKsxAHGDDGGyzF'*....%=-;!)]F///F/////F//FFF{!;-&...%>!2yGDGvHHBtysKr/{~;--$#@...........+#,;~FzxBBxzF);,*+........", +"...@=,;)/zstAGvvBCCCGGvAAsz/);*+...+*-;]/zyABCCvEEEDGCCBAxz/);*...%>;~FsHGLLLLLvCCCACBCBAxzF)-#...&=;)}swGLvGCCCCCCHGvGBBtxr{-#......+*$;;)]FrsxBCCCCvLLHCCyr)*..+#,']ryBAHvvGBCCHGLvGCBAs^)!>@...+@#=;;{IyGvvHHHCCBHGGLDAzF),@........+%-;{QyvLvGs^{~-=#+........@>-!]ryABBvvGHCCCGGvHCBysF)>@....%$;'{/ryACCCCCHvvHCCtxr{!-&.........@#-~]rxtGvDHy/)-=#+...............%=-;{/sAGHDDGsF'-=%........++&#*>-;){F/rstttGGDGyzF)$+...+@*=;;'_)){{){({{{){))~;;-*+...#,'FJtvvHCAyszr/F)~!->#&+.............%=-!]ryBByzF~;>%+........", +"....&%*-;~(:[-'{F/QrIKPJOOOPKzrr/});=%...+#>;_2<8JOwOJJJzKzKzzr^/F);=&...+%*-~1QI5PKKKKKKKPJPPI[F{'-$+.......@&#==-!)]/^IKKJJJJKKr[];#...@%=;~{/[IPJPKKKKPJJIQ[F{~-$%.....+&%$=-_1,;')12[<<<}:_;,@......+@#**,-=,-,->>>->-,,==*#@....&*,014<<[Q}:(~';;->=*@+...............@*>-!{//r2{!,=%+.........", +".....&#*=-;_(2[QQQIQ<[21_!->*%......&*=;(:2[QIIIIPPPIIQQ[}:~-$@...+@=-c|[&.........+%%==-;_1}[IIIIKI>3a(:}QQIIII[Q}}:(!-=%..........@%=3!|[<<2(;,>#@...........+&*=-;!(1[QQIIIQI*&.....@*>;!(|}QQIQII<[}1(_';,=#&..........+%*,;(1[<<<[|!->#@...............+%*=-_45O65Q4:_;=*&...............+&#$>=,,;cca_a_9->#+.......++&%%%**%**$$$%$#%%#%&@+....+&*,90_____;;->=**%@++................+@#=-!:22}_->*&..........", +".....+&#$>,3abe4hhhh7ffdc9,**&......@*=9(ee77hhlkhkhkhk77f:a3$&...+&*90b4khhhhlllhlk5kh47f1a-*&....+%*-9adbf7hhhkhhhh4eda9-=#&..........@&$$-0_b47khhlhhk77eb3%....+#$>-9ab47hhlhhhh4bd03,>*@.........@#=-cabf7hhhhh74bbda9,*&..........+%*,cdf74fdc=*&@............@#*=39ab47hhhlhhh7e003>*%@.....@*=90dbf4hhhhkk7edcc-=$*&+...........&*=cdb47hh4b0,*%+...............+%$=9agjqpk7fb03=#@.................+@#**$>-33999,$*&..........@+++@@@@@@@&@&@+++........+@#=,399333,==$*&&@....................@#=3a14fb_3$*@..........", +".....+@%*>,9cde7kllk74:dc3=*@.......&*-0be7kllVVVVVVVVllk4edc=@...+%=-cb4hlVVllVVVVVVIl9a|7klVVVVlk7|ac9,*%+.........+#*=3ade4kklllh4ebd03**@..........+&*,cde44e0;,$#@............+@%*=90d4klVVlllk7|a33=$#@.....@#>3cd:[klVllhk4ed03>>*%&+...........@*,cae4kkk4b;,$&@...............&#=9afioZol7e|a3>#@...................+@***===,*=**#&............++.+.....................@&#=9993,,,$**&@+.....................@*>3ab44ea9=*&..........", +"......+%#>-')F^zxxtxzr/]'-=#+.......#=;~FrsstttBAABBtAAyyzr]'-&...@$-')/zxtAAAAtBtBABtAysz/]!-&.....@*9'{F^rsxtAtAAAyzrF~;3$@...........+@*=-~]^zsAtBttAxszrF~*....+@#>-')/zsttAAtAxzF)!;-=%...........%=-;)]rzxtAtxsz^/]_;=*@...........#,;~F^zr^{'-=#+..............@*-;)FrsxABAAtyz/)!;-#&......@#-')F^zxxAttysr^F);->*&@............+$;)]/zsxsr/~;=#+..............+*-!{FztvvHtsz/{;-=@....................+&%*$>,,=$$%&@.....................................@#*=----->,=#&+......................+&$-!{/rr/{!-=%..........", +"........@#--!)]F/rr^F{{~;=#+........+#-!){/rrzszzzszzzzr/F]);$@...+%,-~)Frzzszzszszszzzr^F]';*+......%$;!){{Frzzszzzr/]);->%+............+@*-;'{F/rzzzzzr/F])-@......@%>-~)F/rzzrrr^F)'->*%+..........+&%*-;){F/rrr^/F])';-*&+...........&$>;'{]]{~;,%+................@$;;)]/rrzzzr/F)';-##+......+%=;!){Frzzrr/FF));;$#@+..............&>;){F///]);-$@...............@$-']/syBBtz/F{';$%+......................++@%&%#&+++.......................................@%%*=>=*=#@+.........................+%>;')]])~-=#@..........", +"..........&**=--;;;;-->=*@+.........+@*=--;''~~))')~)!!;;-->*&.....+&*>--;~~~)~~~)'~)~'~;;-=*&........+%*=---;!~~)~~!;-,=*%+...............@%$=--;'~~)~'!;;-,%.........@%$>-;;!~';!;--$#@+..............++@*=---!;!;-,--$#&+..............@#==---,=*&...................@#*>--!!~~!!;--$#%+..........+##---;'~';;;--=##+.................+@%=---;-->=#@+...............+%*-'(1}[[2)!--$#+...........................................................................++.+@&@++.............................@#$>-->=$@+...........", +"..........+@@%%%**#%%%&@++...........+@&#**=,,,=$>$>>,===**%&........@%*>>=,-,---$$=$==$=**#&..........+@&##=>,>-,>>=$#&&@+.................+@%#====,>>>=**#%@..........+&***$=$$**%%&@+..................@@@%*%***#*%%&@..................+@%%%*#%@.....................+&%*=>=>$*#%%&@+.............+&%*>>=>>==**%%&.....................@&#**#%%%&@@.................+**-;;~((!;,=*&+...................................................................................................................+%#*%**&+............", +"...........+..++@@@++++...............+@@&&%#%%&++@@&#%&&&@+..........@&%#####%%%&@+@+@%&&+...............+&&%%&%###%&&+......................++%%#%%###%&&@+.............@%%%#%@+@+++.....................++.+++++++.+......................@&@@@++.....................+@+@@#&&@@++++.................+@&%%#%&&&&&++......................++&@@++.+...................+@&*=>,,,=*%%&+.....................................................................................................................@@&@@+..............", +".............................................++.....+++...+.............+.....++........+.....................++....................................+....+..................+...............................................................................................+..+............................+.+++..............................+.........................+++.+++++++++..........................................................................................................................................", +"...........................................................................+................................................................................................+.+.................................................................................................................................+..............................+................................................................................................................................................................................", +".............++@@&@@@@....................................................+@@+.+.........................................................................................++@@@@&@@@@++...................++@&&@@@@++@+.....................................................................................+@@@@@@@+........................+@@@@&@@+................................+..........................................................................................................................................", +"............+@%#&%%&&%@...................++++.........................+@&+&@@@@@@+.........................++++@+++++++++.............+@@@@@+@+.......................++&&@&&&%%&&&@@@+..............+@@@@&&%&&&&&&&&+.......................++.+@+....................++@@++....+.+@&@+...............+++@&&&&&&&&+.......................+&@&&&&&&++...............+@@+@@+++++++@++@@+................++@@@+++............................................................................................+..@+..............", +"..........+@&%#*$*###%@+................+@&&%&@@&&&@+.................@&%&%&%%&&&&&%&++....................++@&%&%&&&&&&&++.........+@@%&&%&&#&&&@@@+.................@&%&&%%&%%%#%%&%@&@+...........+@&&%%%###%%%%#*#%&@+..................+@&&%%&&&@@@++............+@&#%%%&@@@@&%&&&%&@@@.........+&&&&&&&&%%%%&&&&@+...................&%&%%&%%%%%&@+...........+@&&&%%&&&&&&&&&&%&&%&+............+&&%&%&##&@@@@+...............@@@+............@&&@@@+..........@@@@@@+.....@&@@@@++..................@&&%@&@.............", +".........+%%#*$=>===$**&+..............+@&###$****##&@..............+@%#$$*$$$$$****#%&@+.................+&%%#$$$$**#####&+.......@%#%*$*$$*$*$**#*&@+.............+@&#**$$$**=*=*$$$$#%&++.........&%###*******$>$=****#%@+.............@&%%##*$$$***#%&++........@&#*$*$*#$#%#%#*#*##$%%#@........@%%#$$****$*$*$*###@@+..............+@&*#******$$*#%%&@......+@@%##***$#$###$####$#*#&+..........&%*#**$**$$*%%%&+.............@%%%#%&@+......+@@##%%#%+.......+&#%#%%%&@@+@@&#%%#*%%@@.............+@&%$####*&@@..........", +"........&**=,990aaa0c9,**@+...........+&#*==,3333,,==#%............+&*>>33c9cc0cc33,,>*$%@+.............+@#*=,>,,3933,,=,>$*&.....+#=>3339ccccc3933=>*#&+..........+&*>=3393c99c;ccc9933,=*&+......@%*=,,-39ccc0009c9cc933==#+..........+@#*=>,3333333,,>=*#@......&#$=>=333-,=,=>>>,,,3,-,=*+......@%*>>,,339cc9cc333>=$*%&............@##>>33c9ccc939-3>>*#+....@%$==,,3933,,,>==,,,,,,>=*@.......+%*=,3399cc93,=>*#&+..........+%*=>,=>==##&@@@@%*$=>,,=*#+.....@#*=>,,>***%#*$==>,,,>=>#&...........+&**>=,,,===$*%+........", +"......+#,;'){]/rrzzr^F{;-$#@.........@#>;;~))]F]]{{~!--&+.........+#=-!){]FFF/^^/]]{))!;,$#&+.........+&#>-;')){{]FF]{{)))';;#+...#-;))FFFF///FFF]{)~;--*&+.......&*,;~)]FFFF/F/^/^/FF]])~;,#+....@*-;~){{FFFF//^^//FF//]{)!-$+......+&%$-;'~){{FFFFF]{))~!;=@....@=;'){{{F]]{))~~)){{{{{{{);$+...@%=;;)){)]]////FFF{{))!;,=@..........&$-;~){]/F////FFF{));,#...+#-;~){{]]]]{))~~)){{{{{)'-*+.....&*-;~)]FFFF^/]])~;-$%.........+#,;!~{{{)~;->=*==--')){{)!-*...@%=-!)))))';;---;'){){{))';=@.......&#*=-!!)){{{{))!;>$%@+.....", +"......%=-')]/zxtBHHAxr/);,=*&........@=;~{]F/rzzrr//{'->%.........&$-;{Frzzzzsssszrr/F{~;--*&........@#*,-~)]F/rrrzzz///FF]]~-&..@>']F^rzzzssszzzrr/F)'--=%+......%=-']/rzszszszssszzzzr^F~;$&...+#>-)]//zzsszzssssszzszz/F]),%......@#==;!{]//zzszzr///F]]);=+...#-']F/rrzzrr/F]]]F/rrrrr//]-@..+**-~]F^/rzzzssszszr//F]~;=#+........+#=!)F//zzzzssszzzr/]]!$...+$;)]//rrzzr//F]]F//rrr//]'-%....+%>-)F/rzzzzsszr/F)!-$#+.......#>!)]//r//]);;,-,-;~)F^r//F),&..%=-!{F/^/F])~;;!)]F/rr//F]);%......&#*,;))F/^rr^//F)!;-=*#@....", +".....+&*=;!(F[KJwwwOI}{'-=#%@.......@%*-;)(]}}/2/[F]~;-**+........@*=-)]}/2/[/^/Q/[/2{)';,=*&........@#$=-;~_F}/2^Q/[/}]1{_)'-&..&=;){F/2//Q//[/[/}/|);-=$#@.....+&=,;)]}//Q/Q/Q^/rQ//[/F{!-=&....&*-;(F//[//Q/Q//Q//[/[/F])!>&.....@%#>=-;)_F}/[/Q^[F]1]_)~;$@..+%,;)(]}//[/2F|_)|]}//^[/F]_-@..+%*,;)]F2//Q^I/Q/Q//}]|);-*@.........+#$-~{FF[//[/Q/^[//]|~-*...@#-!):F[^[^[}]|(({]}/[//]);=&....+&*-;(F/2^/[/Q/[F_~;,*@+.......&>;~(]:F/})';->>>,-!)]2/2F(~-&++%=,;)]}F:](~!;--!)]1F[FF|);-%.....+&**=-;)(FF}/2F1{~;->$*#&+...", +"....+&$*-;~(}Q56MSSO5[|_->*%@.......+*=-'~|:2[Q[Q[[1(;,>#&........@*=;_1[[Q[Q[QQQ[[[}1(~;->*%+......&**=>-;~1:}[[Q[[[}1}1|((!-%..@,'_(:[Q[[Q[QQ[[[}}:(!--,$*%.....%*-;(1}[[[[QQQ[Q[Q[2[}1|~-=%....&*,!(:[[[QQQ[[QQ[QQQQ[[}|(;-&....+&*$=--!_12}QQ[[[[}}:11(_;=&..+#>;~1}}[[Q[[:|(((:}[[[[2}|(-@..+%$=;_|}}}[[Q[QQQ[[}}1|(!,*%.........@%=-~(::[[[[Q[Q2[[}1(~-#...&$-;(1}2[[[[21(|(|:22[[[1(;=&....+&*>!(}[[Q[QQ2[}}1_;,$%+.......%=-~|:}}}}|_'--=>-;~(1}2}11~-&.+%*,;_122}1|(!;-;'_|2[2[}|('-&.....&#$>,-!_|:}[[[}21('--,$*%+...", +"....@%>,;adb4hloqWqqpgfbc->%@.......@$>-adee77hkhgheba-=*%+......+&>,cdf77hhhhh7hh7k74bbba;,*@.....@%$>9;adbf77kkkhk7h77ffeba;%..@>adf47hhhhhk7hhh77febaac,=*+...+#>3abf7khhkhhhhkkhhhkh7fdc=#....#=9aefhhhhkhhh&..@*30de77hhh74febbbe7hhhhh7ed3@..+&$-cdbe77kkhhghhhh77fbdc9*%.........&*=3a:f7hk*&...", +"....&*,cde4khllnoZqqoml7b03>#+......&*30(e4klXpppnpm7bac>*&+......#=9d4klVVmXmVllVVlliligfbc,#+...+%=-!d:e7hilllllVlVlmimlkkea$+.&>de7hmpXmVVVllllllhhhg7fdc=#...+$,0|49de7lXppmVlk4447ippmVVk4dc*....&*,c(4#&......+#3a14hmnpmh7eb_a~db47mmppl7bc#.@$;abhlXXmmihebb|e7hlmmmiked9#...@#=ccd:f4hihllllVllhhg7fa9,#+..", +"..+%=-!]ryBACCCHGHHGGvGxz/{;,#......%>!]rxtBGDLEEuEvAyrF~-$@.....+>;{^sACAGGLGHCCCCBCGGvvAJF),#...%-;{/syBHGDvHBCCCHHHGvvvHAx/-&.&;/stvDLvvHBCCBBBBCCHGvGwzF'>@.+%-~]rtCCHvDLGHCCCHHGvLDDtJr];%..&,~FzyCCHvvGGHCCCCHHGLLvGCtsF-..+*-~FrsABGGGGHACCCHGvDuvGAAs)*.+*;)/stGGLDvHCBxssyACHGLvDGtsF>+.&=-~FzsAACCGvuvEDHCACCxzF);$@........%-;{ryBCCCBGDLvGCBCBxz{,+..$!]rstGLDLDHCCBAtBvuLvHBBxr(-...&$3~]rxACHGLDHHCCAsz/~;*#+.....+=!]zxBDELvGCAsr^rzxAHvELEvAz(=+@-{/ztvuDDvGtyzzzstHDELLGAyr(>..+#-']rsyBHGvGHBCCCCHHDvGwz]~,&..", +"...%>;~]zxBCBCBCCCBCGDLAs/]~;*+....+%,;]rsAAGGLLELuuvts/);,%.....@>!{rxBBCGvLvHCBCCCCGLLDvs^);%..&$-!]zxCBGGvvCCCABCCGLLLvHBy^;+.&;/sBGLLLGHCCCCCCBCHGDDDts/{;#..%,~FryACBvLvGCCCCABHvEuEGyr];#..&,']zyACHGLLGBCCCCCCGGLvGCBsF-@.&>;)/sBBHGvGGBCCAACGvDDLGHAs)%..%;)/zAGLLLvCCAAysxBCCvLLLvHsF-++@$-']rsABACHvLELLGBBBAyzF'-#+.......+%-;]rsAAACCGGLvHCCBByr]-+.+$;{/sAGLLLGBCABABHGLuvCBtsr)=+...*-!]ryBCHLLLvCBBAsrF)-$&+.....+>~FzyGLuELHBAxzrrsyAGLuEuvtz]$+@,(/JAGLDuELGAszsyBAGLELvHsr)=+.+*-)FztCCGvGGCCCABBCGLDvtzF);#..", +"...%*-;)FrIKzIrIrQIIJwOP}|';=#......+#=;)][QIJOwYwwOPI[{~->#.....+#=;)FQKKJJwJPIIQIKKJwwwOQ(;,%..@*,-_2KJJJJJPIQQQQQQPJOOJPK/)=..@>_}QPOwOJKIIrIrIQrIJwNw8<|'-&..@#>')/QKKJJJPIQQrIIIJwNSP[|'-&...#-!{/IQKJOJPIQIQrQIJJOJJKIF~#+.&*-!1QPJJJJPPIQQQQQIPJOOJKI};&..@=-~:[PJwOJPK^/FF2^IKJOwO8<2_$...@*,;){F/[QIJNNNOII^/F]);-#+.........+#=;)]F/[QKPJJJPKIr/F_;*...%>;(2QPOOJJKKzrIIKPwJPIQ/:);#....@*-;)F[/IJSwOKQ^/]);-*&........*-~1[6YNNNOKI/F]1/QIJwNNN8<1!%.+$;)29!(|Q-'(}[QI5P<22:::}[8YSY64(c=@...%=-!|}[Q-;!~|:[6YSY8<:|(!;->*@..........+@**-;~((:}QKPQQ[[2_~;>&...@$-'(}QIP6K;_1;_b47hlhh4eeeefgmoWWjgba-&..+*>cbiqqqpg7bbd_0aade77hhk7e9@..+*9ab45lmi74bb(ddbbgiqWWoied3%...@*3cdef4hlihf:b:bfgpqWqpfd;=&..+@*,cdefkhlk7e:bbbbef7k4h74b,+..&*3agpqWqmgfbb_a_adbf7hhhk7e3@..@%=9ab4klllk74:b|be7kkllhhed9#...+@%$=,39abfiWUqpgbd0;>=*#@...........+%#=,9c~db7hhhk4f:d0-=&...@$39df7llmih4hlmmVlhhfdac-=#+.....@&$,30dfiqjpfbdac,=*&.........%,cafgqTTTqik7eb1fkimqTTTjfd3@.+*3ab7hlmoWqji774ffgiqWqjgba3#..@#-cfiqWqpgfed(ddbbgiqWqogfd3%..", +"..+&=3cekVVVllQkkkkmnWopk4ea9=%......&*>90de4lmXXXVVVlhed03$&+...+%*,9de7klVVlk74444hpZWWoiea3%..&*9afpZWWoV7ebd_000ae4kkhhh49&..+$cdekVXXml74bdddde4pZWWomkb0#...&>9'bekkllXlk44e44hnZWWpgb0,&...&=30bekhlVVl7e}eee4khkhllk13@..&=9dgoWWZnl4eddaaad:7kllllke9@..@$>cdekhVVVlhk4ee4[klVmVlh4d;#....+@%$,9cdbgpWUWjg:da3,$$&+...........++%$>,90de7lVlhk4ed09$&...@*30d4lVXXVlllVnXXVVl4d03,>%.......&*=90dfmoqmgeba;,$#@........+#-0dgiqTTTWnVk774klXoUTTUjgb9&.+*;aekVXXqWUomlhk74inWWWohed9%..+#3afpZWZnl7eb0addb7nZWWomke0*..", +"..&$;~]zyCBCCCCBCCHHvEDGAyz/);#.....&*=-)]^zxCHHGHHCCCBsr])-$@...+$-;)/ztBCCBCAAtAtABHEEELtsF'%..*,)FKAEELGAsz/{))~){/zsxyxxz)@.+&-)FzxCCBCBysr/]{F^sHDLLGHtzF=..+%-!{rsACCHHAtxssxtAGLLDAK/'-%..+#-!{rstCBCCAxsssyxyABAAttyr;@.+*;(^xDLLvGtsr/{)){FrstAABAy^'@..@>;)FzACCCCCCtyxsxAACHGHBts/)*.....+#,-;~FrsvuEEvyI/('-=*@.............+&$-;!)/zxBCCAAxzF);-&..+*-;(/stCHCHCCHHHGHBCts/(;;>#+......&=-!)2KALDGsz^]_;>*%........@,!]rswEuuuEvHAAABBCHDEuuuus/;&.+=)FzxCCHDEEGHBCCtytvEEEGAz/)$..@>)/JtEEEGtsr^]{)FrsHvELGHts/-+.", +"..@=;)FztCCCCCCCGvGLLLLHtxz/);*+...+@*,;']^zAHGGHCCCGGCyz/);,#...+#-;)/zyACGvGCBBBBBCvLLLLAz/;&..#;)^sHDLLvAs^F)'!;!'{F^^rrrF;&..%-)FzABCCCtxz^F{){/zAAHGBCBs]-@..%-!]/syACBCCAyxABHGHHCAs/{!,@...#-;]rsAABCAByyxytCCAAtszrr{-@..*;FryGLLDHyz/]))')]^zsyyxyz/;@..&=-~FzxCBCCBCBBBBBCCCBCCCBs/)$......&*>-~{^stLELGsrF);,*@+..............@#=-!)]ryACCBtszF);=%...#>;)/sABCBCGvGGvGvCtyrF);-=&.......+*-;)/KtGLtyr]]~->$@........@$!]^stEEEELLHCAAACHvEEEEDGs/;@.+=!FzyCCHvGGCCCCABAAHLLLLAsF)$+.@-(^sGLLvGyz^])'){FzACHHCCBsF-@.", +"..+&=-):rKKzKKKJwNNNNNw8Q2]);=&.....@%*=-'|}PJwJIQIKJwOQ1(!-=%....+*=;)]/IKwwwJKzrKKJwNwNO<}~>@..%,;([6www6Q:);->=>>,-;;;;;;-*+.+&*-_]QIKKr^F|);;--!(2IIKIIr[)=...@*,;)F/QrKKKQ^[I8wJKQ2}(!-=%....@=,;)F^rIzKrQ/}IPOJI/]()!;-*...%-':....#=-~:/KKKKzKKrIrIzKKJKKKI2)-%.......&*$-;_26wNS6[_'-=%@................+@#*=-;)/IKzI^F|'-=%@...&*=-'FQKJJwwwwvwwwK[]);-=*%.........@#,-_:5OwP[1~;>*#&+........+%>!(2%....++&$,;~(}+..@,9_15YSY6}_;-=*$###$$$>>>$*#...@*-;(}QIIQ2|_;->=,;_:}QQQQ[}_$+..@#=-!(:[#@...+@#=;_(2[*&...%-0|[8SSO5:_9-,--3;_((|((~!-*+...@$-;(2QIIIIIQIQIQQQKIIKI[:!=@........@*=-c15YSY5}~-=$%+.................&%$$>-~|[QQ221(;->#+...&*>-'(}IIJwNNNNNSO<1(!-=#%+.........+%*-;b<8O<:(;-$$%@+.........%>-a:[ONNNNwO6<[<;([QIKIIIPPJP<[[[8YNM6[_;-%..@$c(26YSO5:_;->*=,9_12[QQQ[:!*..", +"...&*,0b4hlllllppoojoomi4fba3=&.....@&$-0dbfkmmkgffgipmgfba;,#+....%*30bb7ijWqplhlhVmjUUUjgfa>+..&,cdfpqUqpfd9->#%%%%%##$*$$&@...&*30b4hhh74bac,,==3cbf7kkh7fd$..+&*=9abe7ijqjjjjjqqqifda9,*#@...+@*=;_bf7iqqjjjjjqqjgbac,=*&+...&3abgjWUqibdc-,,;0dbeeeebda3*...+@*30d4hlmmllklhllhlVmmlh7e0$+.......+@%=90biqWqpea9*#&..............+@@@@%*$3c_e4hk4ebdc,*%....@*=9ab7hmmZZUUUUWqifdc-$#&@.........+&*,cbijqiea9-**#%+.........&>9abgjWUWqoppjjjppooWUWqif0*..+#3;b7hllhhhmjoopi7gpqWqjgbc3&..@=cbfpqWqiea33>$=,3adf77kk7e0$..", +"..+%=9~ekVVXVXlXXXXnXXXVk4edc=%....+&$,cd14klVVlkkkhlVVlke:ac=@...+#=;a:4hmZWZoXVVXVXZWUUqp7b9&.+&3ab7pWUUjgdc9*#%&&&%%%#$*%%@...@=ca:kVVVlk}d;3,>33a:7khlll7b>+..&$3c_e4ijUTUUWUTUWqifb09,>#@....&$=0dekijWUTUUUUUWoiea9,$#@...+%;afioUUqp4bac99a14kkkhk74|a,@...%=9_bklXXXXXVVlVVVVXnnXVhea>+........@$>3aepqWWpgd;3*&...........+@&%#$*$*$,9ab4hllk7eba9=&+...@#3;dekVXnZUTTTUUqm7bac,*#@.........+%*-0eioqp7ba9,>$##%@+.....+#,9d4ioWUWZXnoqUUqnnoWWUZp7a=+..#,a1kVVVhhlmZWZonlmnWUWogea3%..+>abhpWWZpfac3==>,3a:4kllVl7d=+.", +"..&=;~FzABHGHHCBHHGBGGGHAys^{;*....@*-'{^sxBBHCCBAtACHCBAys/);#...&>;)/zxtHvEEGHBCCBHGEEELGxz(%.+*!]rsGDEEtJ^]~-,*%%%#$#*$*$$&...#-)FztBBCAAs^{~!;;)]zyACCCAx^;+..%-;{/zxHDEEEuEEEEEvGs^]);-,%...+%-;{/stHDuEuuuEEEELyI{'-=%+...+=)/ztDEEDAJ/]))]^sACCBCCByz/;&..+*;)/ztBHHHBCCCCCCCHCCHCCtz];@........%,;_:KHuEEGs[{!,#.........+&#>---;--;-!)]rsABBBxsr]'-*+..+&=;)/stCHvLEEEEEELGys^{';=#+........%>3!{ItEGGszF(!;----=*&+...@='{/stDuDLvHGLEEEEvGvLEELGJ/-@.+>!]zxCCCCBBGLEEvvHHvLELvAKF~*..@;FKyGLELGs/_~;;;')/zxtCCBCx^-@.", +"..&>-)FzACBCBCCGBLLLvLvCCBxr{;*+..@#-;)FzsAACCCCBtABCCCCBxs/{;*+..&=;)FzsAHLLGHCCBCCCvLEEGHyr{#..*']IsGLELHsr]~;,$#%###*$*>*%&..+&;'FzyCCCCxs/{!;;!)FzxBBBBCy/;+.+%-!)/sttGEuEEEELELLtsr/)~;-*+...*-!]^sytEEEEEEEEEEGx^)!-=&+....>~[sAGLELAs^]{)]rsAACCCCAxz/'*...%;)FzyCCBGCCCCBBCCCBGBCCAsF-&.......+%>;)FItELuHs^);-%+........&$--;!)~~';;')]rsBBCAysrF~-*....%,;)FzABCCLLEEEEELHtzr]~;-$@........%,;)FKyGLtyr/{~;;;----=&...+*;)^sBLLLLvCHLEuELvBGLLEvts^;@.@>!FryCCHCCCCGLLLHBBGLLLvtzF)$+.&-FrxGELuHs/{';-;~{/zABACBAy^;+.", +"..@*=-_]QKKJKKPJwNNNNwwJKr/|'-&...+&=-!)2^rKKJJJKKPPJJJKK/}{;-%...&%=;_]}[IOOJPKIIIIKJOwOOPI}'*++%-!([8www6Q|!-=*%&&%##*$###&@...@=-~FQKKKI/F(;-,,--!]/rrrKr/)>...@*-;)1^QPORSMYwwwNOK[1{~!-,%....@*,;(F[Q6YRSMMMSwwOQ(;-*#+.....#;~:+........@$=;_25SNw6[(;-$&.........&*,-!!)~!;----!{/IKr^^F|'-=%....@%=;)FQKKJJwwSwwwO8KQ1);->$@........@#>-~:5OO6[|)';->--;--=&....%>;_:IOwNwJII6wOYOPIPwwww6[)=+..$-'F[KKJKIIKJJwJPKPJwNwJ<2),&..@=~1[6wNw52);->=,-;)F^rrKzI/_>..", +"..&#=-;|[QIIIIIPOwwNwwOP<[}(;$%...@%$>;(2[$#%%##$$**$$#&...@>-!|}QIQQ2|~;->>,-'(:}[QQ[2'*+.+@$=;_1:[~|[6SSO<:(_!'_|}QIPOO8PQ}(;%...&*-;(}QIIIIQ[22}}[[-~15YSY8}(;=*&+........&=-;_1:}(!;---;_1QQQ[:|('-*%+...+$,-!|[IIII5I55686665[(!-=*&........+%=-c|<8O51(~!---;~~!;,#+..+&=,a|7OSNY8<[<58P<[[<6SSO5}~>+.@#>;(2QII$=-;_:}QQQQ[2~=+.", +"..@#=-0b4hhlhhhVmpojjomlkh4bc$@...@&=,cb7hlmoqUUUUqTUUqjmlhfd,%...+%*-0bef7hhhf1bbbbe7hhhkhkfa,++&=cafiqUqpfba3,***#*==-3==>*&...@=9cb4klhhf1a0-,,-9abf7....&*=9abe4khlhk77ipjpg0-*#@......%,0bgjWWqifbdaaab4hmoqZqpi4ec*..+&*90bfkllhh74eb:bf47hlhhkea,%.......@&*=9cfiqWqifd;3*&+........%,;de4hk7ba;;9cdekhk7eba;-*%+...+#=cab7hmmlhh@...%>9abgjWUqige7khkh7fijWUqif0>..+#30d7hhlhhkhhhhhhlpoWUqjgbc,+..+$0dgiqUqieba;3=,3cde7h5hk7e0*..", +"..@*>3a1kVVVVVlVVVXXXXXVlhk:a>@..+%$,cdfhmnoqWUqUUUqUUWqnmVke0#...&$,9d14klVVk7e:e:e47+.&,cbfpoZojh4bdc;,>,9cdddda'0>+.+&>c_ekVVVhk4ed~c;0a17hmlllhfa$..+@*-cde4klVVlhkklmonmhk[44e(,@...@*-01e7klVlVhQ4hmppfa,$#@......%9dbhjWWqmhee|b1e4lVnZZZnVh4a*..@#99d:kVVVllk44eee4klVVXlhedc%......+@$>3cdfpqUWjgba9>$%@.......%9ab7lmml7:bb(bekm6Vkke|03$%+...&*3cdekVXXmlhhhklmnonmh4|dc$@......+#=3cdfmoZphebb{de7hhhfd9*..+&-0(fmoWWZpk47khhk44ioUTqp7b,+..#-0:kVVVVlhkhkhlmXnZWUWohfa>@..@>cbgpqWWphe1d!09~dehVmmVVke0*+.", +"..%>;~]ztCBCCCBCCCCCBCCCBCyz/!$...*3)]rxGDDLvDDvvvvvvvDvDvvts]>+.@#-')^zyAACCByszzzzstCCCCCBy/;++%;)}stvvvHCAsr/])))]/zxyyszF~&+@*;{^zACBCCAxsr///^rstGvDGAxz{-..+%-'{/sxACCCAByxABHHBBCCBAAz~&..+#-']rstACCCCtyxxAAs[(;,*&.....+*;FrxHGDHHAyszzrzstCCGGBvCAs]*..#>'{/zACBCCCtxszzsxACCCCCAsr{=......#$-;~F^sHEEEGxr/{!->$&+....+>)/stGGvGtszr^rsAGvGBysr]!,*+...#-!]rstCHBCBCAtytCHGGBBAsr];#.....+#=;'{/KAEDHxsszzzsADHGyI{=+.+$;{rsALEEDGysssssssxtDEEEGs/;+.+='FzxCCCBCCBxxyACGDLEEEvyKF;@..%!{^zAGGGHCxsr/F]/rstDGvHByz]-..", +"..@$-)FzyBCCCBCCCCCCCCBCCBtzF'$+..#;)FzyvLELvCCBBABBCCCvLELGz]=..+*-!]^syBCCCBAyzzzzyAAACACBx^-@.#-~FryCCCBCAAz//F]F/zABBABxz~&+@*;]rstCCBCBysr^//rssHvLLvAy/{=+.+%-!]rsABCCCCAxyyytABCACBBAz)&...#-)FrsACCCCAAxssssrF~;=*@......*!{/sACCCBCAyszzzssxCCCCCCAs)%.+%-!]rsACCCAAByszrzytBCCCCAsr{-+....#>-;~{/IsGLEuvyr/{)'->*@....+=)/sAvLLvAyzzrzstvvvAxzr]!-#+..+#;~]rsACCCCCBByxyxCBCCBCtzF'=+....&$-;){/JAGLHAszzzsytDLDtz{-+.+$!]rstvLLLBxszzrzzssBHLLvGs/;@.@=)FzACCCCBBxyssxALLEEEuvtzF;%..%-)/zyCCCCCByz///rzxHLLLvAsr{,+.", +"..+%=-~]QKKKKrIrIrIrIKrIr^/|!-&..+&>;_:+.@>-'{^KzKKKr//|_({(F//rrrr[];@.+#,!{FQKKKzI//1|{1|}QPwwwJI2);%..+&=-']/^IIKPKQF//2/^^rKrzIr};&..+%=;)F}^IrKKI^F2::1)!-*%@.......&=3_]QKKKKKr[F1F:F2/rKKzKIr};&..&$-;)F/KKzKr^/F]]]/^IKKKKI/|;*...++%=,-;'_:[6RNw8Q1(';-,*#@.....*;_25wwww@..+*-':^KKsKKr^211||2[6wNwJ<2)-%..", +"...&*-;(}[QIQQ[[[222[[[[}:(~-=&...%>-_1[8SNRO5[[2}}}[<5OMNR5e!&..+%>-!|}[Q-;~(b:45MNR872|(~;->*%+...+&,~|<8SRO5[2e:1[56O5[|('-=%@....&>-'(}QIPI..+%>;_2+..#-~|[IIP5<[2:(123a17khh9afiqTTUqjmg7f47imjWUTUjg0%...&=;dfipjjqqjjijiijijpppmhf|9%..+%*,0be47kkhiiiiiiiiikhh7eea-+.+%3abgijqqjjpppiiijijjjpmiba9>@...%>9bgipjjjqjpjijijipppppi4f9@...#>9bgipjjqjjjiigebac=*@........+%=9ab47himmiiiiipiiiihh44ba>..+&*9abgijqjjpigfbbfgipjZojpge0*...@#*9adbfgijqTTTqjigfeba9=*@...+%,0dgmjojpjpiiiippmieba;,*%@....&=;aegpjqjjpigebbee7lmpjpied=+...@*=9afgijqUUqjimippjqUUqjf0$...#3cbgiqTUqig:bacade4iqUUUjgb,+.+%3abgjqojjigfbbfgmjWTTTjgbc,&...&*30be7himppiiiiijjpppmgba;#+..", +"..+@*>-0b4kllmVl5ihhihh7f|dc3#@...%-afhjqTTTWWZmhk7lpZUTTTTqhb#..+#,0biqqUUUUUTqUTqUWWZWZXifd9#...&$,cd1e4hlVnqqUUWqonmVll7|a,@..#;b4mqUTTUWUqUTUUUUWonmlhba9$+..+#3afiqqUUUUTqUUUUUWWWWWonl40%..+%9afpqWUTUUUUUqoifbc,$&.........&$9!be4hmoWWWWqqUUqXXllk410$@..%,cd4ioUUUWWZph447moWWUWWqp4b=+.+&*-a1khiqqTTTTTTUZpmhked9*&....%>9afkmXnqqUUWWZoXmked03>$@+....#,ab7pqTTTUWom4e1e4hioWWWqm4c&..+%$9agjqUqTTTUWWqWqUUTTTUjg0=+.+%,_ehjWTTUol7ed~ab7hjqTTUqmf9+..$9d7pqUUUTqjhfefkljqTTTqiba3#...&$,9de4kmoZZWWWqWqWomVl7bc3*+..", +"...&=-;)/zsAHGDvvvvvvvHtsr/{!>+..+$!FzxvEEEEEEDGAxxBGDEEuEEEA^-+.@=;|IwEEEEEEuuuuuEuEEELEvxz:'$...&*-!{^zzxACGDEuEEELvCCAxs^{-@..*)rsGvEuEEEEuEuuuuuEDCAts/(;>+..+>~2stEEEEEEEuuuuuEEEEEEvHts]*..@=~}zGuEEEEEEuEEvAs/{3>*+........&>;)F/zsHvEEEEEEuEEvHCBxz/(-+.@*;{^xBDEEEEELHxssxGvEEEEEDHxF>..+*-)FsABGDuuEEuEuEEGHAAs/)-*....#,'{rxBHHDEuEuEEGCBsr]);>=@.....$;]zxGEEEEEEvAJrrrzsADuEEDtsF=..+%-!1VNEuEuEEEEEEEEuEuEuuZz:-..+>~FsAGEEEDDHxz/]F^sAGEEEEDts)#.@>~/zHEEEEEuGJrrzytGLuuuuXI];*...&=;~]/rstDDEDEEEEEvvHAyzF~;=@..", +"...&*>;~{/zytvvvuLuLLvvAsrF);#+..+$;{rsAtDvDGHHtszsxAtGuvuvAx^;@.@=;{/JtvtHvvDvDvHGGGGHGAAz/]!*...+&>;)]/^zsxBtHGGGAHBAyyz/])-@.+%!FzxyvvGDAGDtDvHvtCBtyz^]~;$+..@=!{rwtGGGNvDDDGGGvtHHAHBtsr(=+.+$!{IytGGvGGvvGHwsr])-$@+........@#-!)F/rsxGtGHGGGGBABtsz/]~=@..&-)/zxAHDvGHGxyzzJytHGHHHBtz]>+.+*-~]rsxttGDDEuDvGGBBAsr]!-#+...%-;)FzsxttHHHHHGHAxz/);,$&+....+%;{^sytvvNGvAsr/F//zsxGtGtyz(*...#>;]zXHvDDGuGGHHHGGDDDDAy^(=+.+$;]rsAHvvGAxs/]){FzyAtDHNAyr)%++>;{^JtvNvHGxz^/^zyytGGGxzF{;*...+%-;)]/rsytGtHGtHGAAszrF{!-#+..", +"...+&&$,-!_:Q5JOJOOOO66[|_;-*+....%=;~(:}e}}}}::{(((:}}4}2221~=+.+%=-~{e2442}4}}ee}e2}2}:1(;-*&.....&*=-;;))1:2e}}2}22]()~;;>%+..+*;'|ee24[44[eee24}e:{();-=*&...+%=-'{ee}4}4}}4}4}[4}2}}:](~-&..+&=-!1e}f[}4}}e2:1~;,$@...........+#=,-;!(|:ee}4}}}2:{))';-=%...+%-'(|}42}}e2:1(({:}f}}}22:);%...&*,;)1]2}e:}424[4[}F]{';,#@.....%=--~)1:e}}4[[}2:(~;->#@......+&=-~{:e4[}}}e1~;;;'~(|e1e:|_-@...@$>-~{:ee}}}}[}2}4}4}}}e1'-#..+%>;~{::e222:(~;;;;~(1ee}}:|'>+.+#=,~{e}}ee2:(~!~)({:e}e|_;-=&.....&=,-;;)11:e}24}}2:(~'--=#@...", +".....@%*>,-0_::4[[[[22:_;->*&+....@*>-;0____(__!;9;c'_______'-%...+#>3c~__(((((_((_(___a!;-,*%......@%##*>-9;;'_d____'!;-,=**&...+%*-3c__(((({((d___!!;->=*$&+....@%=-c~__(((((((((((d____';-*+...&*=-c__((((d(_~!9-**@@............+&**>>-9c_~_(((_'!;--==#%.....%=-9a__d(__a!;-990__((_(_!;$+...+%*,-c~~__|(((((|((~!;-=*&+.....@%$>=-;!___((|(~;;-=*%&........@*=-9~__(d__~;-==>>-9;00~!;-#+...@&#>-9'___d((((((((((__~;-=@...@*>-9!____~';-===--30'__~!c-#...@$>-c~___'_!;-,--3;!'_~!-=*&......+@&**=--cca_____~;-,==*#@....", +"......+@**=-30aabbbba009,,*#+......&$,3390a00cc93>-339000c9c;,%....&*=330c00aaa000c009cc3,=$%@......+@@%#$==,39c000ccc3-=$$#&@....&*>-39900aaaaa0c0c93->>$#%@.....+&*,39ccaaa0aaaaa000909c;3$#....@#$>9990a0a0a0c9>>$%+...............&&#$=-,99cc090c93>>$$&+.....@#=-3900a00;39,>33909000993#+....@#$,-9cccaaaaaada00;3>*#@......+@%$$>3390aaaaac-=>$#@+........@*$-339cacc9c-,>*$*>>,9993,=&....+@%*-93900caaaaaaaaaacc;3=#+...+&$=,339ccc9->$**=>,-39cc3,=&...+%$>99c0c993=>*>>=,93c93,*%@.......+@%$*>=-,999cc9;3,>$%&+.....", +".......+@**=,3300a00039>=$#%@......@%$>9933099-,==>=>3333999,$&....+#$=3,9339993,3cc999,>=**&+.......+@@&#$$>,,333399,==**&@+.....@#*=,999cc90ccc939->,***@@+......&%*-93990;99990c33393-3,=*&.....@*$,-93ccc9c93,,*%@.................@&#**,,99999-3=>***&+......+&*>-93999333==>==399,3393=#.....+@**>3,9999cc0ac0c-3,=#%+.......+&%**>,999cc0c->=*#%@..........&#*,99ccc9-,,**$#$*===-3,>*&.....@&**,93339990c000;cc99,=*&+....@%$,,399,-,==*#*$=,,-9,3,>*@....@&==9339-3,,*$**>*=,,,>$$&.........++@%*$=>3993-3,=**%&.......", +"........&**>-3;!'__'!9->*#&&+......+&$>--;;!;;,>>$$---;-;;-;-*+.....@#$>=>----,----;;;--$$%#@............@#=>--;-;;-;-$*##&@......+%*$,-;;;!!;!;;;-;--=$#%@+.......+&*,-;;;;;;;;;!;;;-;,-3-,*&.....@#$=-;;;;;;;';-,=%+..................+&*$=,-;-;---,=##%@+......+&#>--;;'';--,=,,--;;-;---$&.....++#*>-3-;;'!!'~!;;;--=#@.........+&#%$=,;'';!;--**%@...........+%$>-;;;;;--=$%%%##==>,-->%+.....+&#$-->-;;;;!;!;;!;!;--=$&.....+%#>-,-->-=**##%#$=-,----$%.....+&$,--;---$$*#$*=>>=-,>=%&............+%=,----33->$##&@.......", +".........@##*$===>----$%@+++........+&%*==$=>**&@@@&#*$=**=*#&+.....+@&@&@&@@&&@@%==$#*#&++.+............+&&%#$>-,>>##@++.+.......+++%#$$$>>=>===*=$**%+............+@&$=>--->=>=>---,==**%%&@......+&%**$==>==$#*%@+.....................+.@#$=-,->=%@+...........+&%**$$=,=>***##%*$=-,>$%&+......+@%#*>>=>$>>---->,>*&+.............++%$>>--==$#&++.............+&#*$===$*%&++++.@@#%%%*&+........+&**#%*==========$*$%#@+......+%#==$%&@@@++++@&#%**$$*%+.......+%*=$-=*%++++@%&%%%%#&&+.............+&%**=$**%@@...........", +".............++@+@%&&@+................+..+.........+..++++++.....................+.+++......................+.+@&+@......................+++@++++.+....................@@&&&@.+.+@&&@+..++..............++++@++..............................++@@&++.................+...+@@++.....++++&@++.............++.++@&#%&&@&@@...................@+@@++....................+.+++++++........+...+.................++++++.++++.+.+.............+............++................++@@.......+.++......................+.....+.............", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"...........................................................................................................................................................................................................................................................................................................................................................................++@+@@+@++.................++@&@++............................+@@&%%*#*##%&+...........................+.............................................", +".......+@&&&&%####&&&&@+....................+@&&&&&&&@+................@&&&&&@+++@&&@@+.....................+@&%%&&@++..+.............+&&&&%%#&&&@+++@&&@+................+@@+.......+@&&@+..............+@&@+.........................+@&&&&@+.....+@@&@+..............+@+...+++...+&%&&@@+..........+@&&&&@+...+&&&&&&&%@+.............+@&&&&&&&&&&&&+................+%$--;-;-;---$*&+..........+%*=>>-,$#&@+........................@#>-;!'''''!;-$&+..................+@&%*#**#%@..........................................", +".....+%#*==$>===$>$=>=*#%&@...............@%#**=*=*=*%&@.............+%**=**==$*#*=*$*&@.................+&#$*=*>>==##**%#@+.........&##=$>$>=>==*$%$$**$#%@+.......@%##*#$**#%%#####$==*$##&.......+%#*#$==*##&@@@&%####*##@........@#$***=**&@@@@&&*=**$##&+......@%##*#$#*##*#####$=*==*#&+.......+%*=>=*=######*===$>**#@..........+%#==$>=>=>====*$$##&+..........@#,-'){]]F{{);;,*%+........@#$--;;;--==*&@.....................+&*,-!)]FFF]])~;-$&+................@#*=,,-,>=*%&.........................................", +"....+@%*$$=**=$===>*$>**#%%+............+&&%*$$>$>*$$##%@+..........@&%$$>>>=>=>=$>=*$#&+...............+@&%*$=*=$=>=$$**$&@.......++%$$**=**=>>>>>=$>>$$*#@+......+@%*$**==**&&%%*****>$**%%+.....@@%#****$$#%%&%&&**==$==*&+.....@&&%*>>$$*$%%&&&%%$$>$=*$%@.....+&%*$=*=$=******>*$>$$>**%+......+&***$>*>=**#$$*$*=*>>**&........+@&**$=*$=>*>>$>>$*=*$#@..........@#=-!){]]1](~;->$%@........&*=---;;-->*#@+.....................@%*>-;){]}F({)!-=#@.................@#$>=,-->$$%&+........................................", +"...+%*$>==,------,-,,=>***%%+..........+@&%*$===,=,,=>**#%@........+&#$=,,-,---->,=>>***#@.............@%#$$>=>-----,,=>=*$%&......+#**=>,>--;-------====$$%@......@#$$=>,>>>$$$*$*>=>>==>=$#@.....&#*$>=,>===*#$##$*>>>=>>>=@.....&#$$>===,>***#$#$$====>==*&.....&#$>=>>>,>,=>>>>=>==,,=,=*%.....&**=,>,=,=>>$=*=>=>>,==,=#+......+&%%**=>--,--,---,,=>=**%..........&*=;_|}}[221(!-=*%@.......+&*,;_((~~;->$%@.....................@%$=-;_|}}}2}|_-,*&+................@#*,-;';;-*#%@........................................", +"...@#*=39cc00c000cc9c9c3,=*#&+........+&#$>,3cc9cc9933>=$*%@+.....+&*=,3ccc0c0;9cc9993>=$*&+..........@%*==39cc0c0c0ccc93,=*#@....+&*>,399c;0aaacaac0ccc33=$%+....+%*=>3399933=>=>,9399c93,==&....+*$=339cc9933=*>*>=33c39933%....@%*=339c9393>=*=*=339c9c93=#....@*==9ccc;999933339c999c993=%....@*=,399c99c3939339c39cc993=&.....+@#$$>339c0c0c0c0ccc9993=*@........+%>3abgiimk7fba;>*&+........%*3abe7ebdc3$%&+...................+&*>-;0bf7iiiigb0-=%+...............@%$,;a_bba0,*#%+.......................................", +"..+#=c0_bbeeeeeeeeeeb|bdd09>#@.......+#=,99adb|eebe:bdac33>*&+....@$3c_bbbeeeefeeeb|bdcc3=*#@........&*=39ab1beefeeeeeb:bda3=#+...@#-9db:eeeefeefeffeeebbd03=%....%#3c_bb:b:bda9c!a(b1eb1da09$@..+%=-'db1beb{b~0c39c!d|b:b|b03&..+#>c'db:be1b_acc9c~db:b1bbdc>@..+#=9~d|beeebe|bd_db|be1be1b0>+..+%=cadb:b:eb:b(ddd1be1beb1b0,+....@%*,cc~dbeeeeeeeeeeeb:bd03%.......+@*>9bgoqZonVl4bac>$&+......+#,0|7kkk7e{03>*&...................+#*,ca14;{^stAGttGtHHGHAGtGAGty/{;$+..@-;{/sAtGtGtsz^^rzyyABGAtszF;#..@=']rsABtHtAys/]{]^zyAAGHAAsF,+.@,!FrytAHtAtsr/FF/rsxBBHAAyz],++#,)FzytHtAGtAtsszsyAGAHtGAyz)&..@-'FzytBGtHtHyszzsxAAGtGtAyz{=...@$-;)/zsxxtHAGGAGHtGAHHAx/{=+......&#-;{rwvuELGHBxr])->%@......&>;{^sCCCAys/);,=@.................+@=-'{/zyACGvEEvxI{'-$+............+*-!{/zxGGGts/{'-*&+.....................................", +".+*;(/xGLLELuEuLEELELHCBAs/{!>@....&*-;)]^zyACGLLLELLAxzrF)'-*+..+$!FzyCLLLLuLEELLLLHByz/{);-$+....&*-!)/ryHLLLLLELELLLLDHs/);*..&,)FKALLEuLEELLLELELELLLts/)-+..%-)/KtDLELLGCszrzyBBGDLLHBxr)=+.%,)/sBHLLLGCByz/F/zxAHDDLGHyr;@+*-)/sAGvLLvCByzr/rsACHDLLvHy^;@+#;)/stHLLEEELHAxsxHDLEELLGAx{#..@-(ryBGDLEELvHtssxGLELLELGCx/-+..%>-!]zyBCHLLLuELuEELLLLLAz];@......@=-~{IxEEELGBBsr]~;>%+......&$;)/zyBBCBxr]'-=&..................&$-!]/zyBCGLLLDAr{!-#&............%$;~]^JALLLGyrF~-=*@.....................................", +".+%-'1@....&*=-;){FrKJwwNwNwwPI[F('-=&...+#-)}QPwwNNNNNNNNNwJKQF_~;-=*+...+&*>-!|}...&=;([8wwNNNwPQ1FF[IKJwNwJP^]!%..%=;(/IJwwNwJI^F_)|FQKJwNNwJIF-++&,;{[IJwNwwJK^2]{F[IKJwNwwJI]>+.&,'{[KOwNwNNwJI[[IPwNNNwNwK[~&..+*;{[IOwNNNNwJI}[QPwNNwNwwP/(=...&*--~FQKKwwNwNNNNNNwNNww8[_-@......@%>;':%+............&*=;~:[8SNw8Q:);,*#@.....................................", +"..#=c(!|Q5YNNNNSNNNSSO8[Q11(!;>*&...+&*=;~(1<8YSNNNSNNwNNNR67(;,@..&=c_e5YNNNNNNNNNNNNNNNNS841;*+..&=3a28YNNNSO5}1(|}Q5OSNSO<[|;*..@>-~}<8YNSOPQ}|___1[+.&=3(}<6SNNNNR8<}}46ONNNNSOP[!&...*;(2+.+&*>;_1[IIPYNNNNSNNNNNNNY84_,@......+%*-a1'_:2[QQ[('->*&...................+%$>-!_:}5OSSO<1!-*#@...........+&*>-~_[5YNR6[|~;>$&@.....................................", +"..#>cbfijqoooooojojojpih574ba;#...@*=3abbf7hhmpjqjqojpiiieba3*+...#,0b4iqUUWWoqqjjopmhh4eeba3*@...@#>3abe7hpoqjqjqjoqqWUWjgbc3&..&=0dgmqWUWqqTTTTTUqqWWUWjgdc$+..@>3aepqUWWqmgebdbe4hjWUWji4bc$..@>9afgpqUUqi7fbd0dbfhpqUWqmgb>+.%=3dehpWUUomh7feeefkipqUUqigb>+.&=3degjqUUWqqigfegijqWUTUji4c&..+*;dfgjWUUUqqigff7ijZWUUWjif0>...&*=;deklVmoqqjqqqqqWUUUqpfd=+......@#*-cbgqUWqgfd03>$&@........+&$=;a|e4iikfa3=$#+..................+&%$=39degjWWqibc9$#+...........@#=3;dfgijqqpgebc3$#@.....................................", +"..&,cdeimXnnnnmmVlmlmmVVVVhkea$+..%=9d1kklllllmlmVmmmnoqojhba>+..+%-cd7ioWUWonXmmmlVVVVllh710,%..+&*,c17klVXXXmmmmXmnZUUUqhfa9%+.@3cb7pZUWqojqTTTUqpnoWUWogea>+..&=9d7pWWWZml7eb|be7hnqUWol4bc%..&=cdehnWWWom4ebddb:ehpWWUZmh|,++&>cdehnWWWomhkkkkkkklnWUWZm7b,+.&>caehoWUUWonVk444lnoZWUUoi40&..+#9aehjWWWWonlk74klXnZWUUolfd$...&=30bklVVXmmmmlmmpooWUWWjgd,+......@#>90eiqUUqifdc3=#@..........+*>30aehmomgea03=%...................+%$>-0aeiqUTqifa3=%&...........&=-9deklXnnXXVk4b03=%+....................................", +".+*3':rxBCBCBCBBAAtACCBCCCCAxF=..+$!{^sBBCBCBCBAtyAACHDEEvAs/;&..+$!{/stvELLGHBCABBBBCBCBCBs/'$..+*;)/sACCCCCCBAtBBCCvEEEDyK/'$.+*!]ryDEELvGHvuEEEEGGDLELGxKF-@..%;_/sGEEDDHtsz^^rrsyDDEEGts^)>+.%-~FzyHDEEGByzr/F/rsyBDEEvHs/;++%;)/sAGEEEDHABBABBBtBGEEEvHs^;@.%-~]^sGEEELGHBAxAtAHGLEEEDtz0%..@-)]ryGvEEvvHAtyytBHvDEEEGAz(-...#-!]rxCCCBCBCCABCCHGDEELGs2!&......@$3)FKtEEEDts/{'-=@..........+*-;~]^xGDvtsrF);-#+.................@#=-;{FzxvEEEAKF~-=%.........+&$-'{rsACGvHHCCByz]);=&....................................", +".+%-!)^sBACCCCBtyyyyABCCCBCCxF$..+>;]zyCGHCCBAxyssxytHGGvvAsr~%..+*;{/zAHvGvHCCtAxyBBBCCCCBy/)=+.+>;{/sACBCCCCAAyxxtBHGLvHxzF'*+.*!FzJGLLLvHAGEEEEGCCGLELvyz];@..*;)}zAGGGHCBsr/F/rzxBLLLvys/)$..@>'FrsBGGvGtsz^F{FrzyAGGGGtsF-@.*-)FzxHLLLGCBACGGHBBAvLLLLAsF-+.&>!{^KxGLvvvHCBBBCCBHvvGvHx^~&..@=;]/ztGvvGCBCBxyACCCGGGDAs/)$+.+%-!]ryBBCCCCBABABBHGvvvHtzF-&......@$;~FKAGLLLAz/);-*&...........&=-!)}stGCByzr]';$%+................+&*,;)FItGLEEtzF~;=#+........@*,;)]zACHHCCBHCHBzF);-*&...................................", +"..@$,;)][IIzKK^FF]]]F^rIKrKzQ~%...$-~2IOwwJKQFF1{{]F[QPPPPIQ};@...&=-~|[KPJJKI^//}F/^IrKzKr[{;%..+#,'{/IKKKKrQ//2FF[/IPPP<2{~-&..&-;1[6wNwJI+..@>;(:IPJKIQ/2)';!~1[Pwww8Q:);%...#>'(1IKJPKQ[]_~!'(12QPKJK[:!*+.&=-~{[PwwNJPIIJwwwPIIJwwNOI2)>@.+%>-'{}[IPJwwJJKKKJwwJP<<[:'=+...#=;~1}Q#@.........@#$,!_:3ae4hlik44ebb:bf47hlhhed3#...&=9de7hlhh47ebbbbbefhk74bcc$&..@>;afpqqqpggiqUTqiggpqWqpfd0*...@*30b7hhhkfbd;3,-cafiqWqpgdc3&..+%*,;dfhhhhfedacccabe4hlh7eac#..&*30dfiqUWqigiqUTqjiipqUWjgbc*...+%$=90df7ijWqpllmoqqpgfbdc,%....+%*,9abb47hhk4f7gppqmgfba9-&....+&*,cdbf44eeeeefgpqqpgfba9>%.......+&*3abiqUUjgd3,$&@..............&*>cab77h47fba9=*&...................@*>9dgjUWqib0-=%@.........+%=-9dfiqjifffipjpfbc3>*@...................................", +"..&#=9ab4klV6lk4:bbb:4klllll7d#...*cd7mqWUoi4bac399c(4lVVVVlkb=+..&$=9aellXXmlkk7447hklVVll7d9%..+#>9dellVlVVhhk77447kllh4edc,%..&*3abioZZm7gpqUTUjghmoZqpgdc$+..&=30ekVlVlk4bac99cd4mWWZjhba,#..@#$30d4lVVVk4f1dd_be4lVVVl4d3%..@=9cb4pZUWoXVjqTTUjmljZUWZifa*....&*>30de4moZZnmmnqWZphfbdc>%....+&*=3abe4hVllhhhlnZomhebdc>%.....%*,cd|4f444447kloqoj7fbac=&........%=caeiqTTqib0,=$@..............+&=30b4kllVk41a9>#+.................+&$,cbgjWTqjfa9>$@.........@#>90b7moom7e4inopgbac=*@...................................", +"..@=;~FzstACBBxszzrrzxtCBCCCyF$..+-{^sHEEEGts^{~''~)/sACCBCAxr;@.+%-;)/sACHHHBBCBtACCHHCCCAs^)=..+$;~FzyACCHvGHGHAABAAAAtsrF(;%+.#-;(^JtHGtxywEEuuGyxAHGAsQ{~>+..#-~FztCCCCAsr])~~{/JtEELvyKF'*..@=-;{^sBBCBCAxzr^rzxtCCBCts/'*++#-~]^sHvEEDHGvEEEEEHHGEEEvAz}-....&=-!)F^stHDDvGvDvDvAsz/]~;#.....@$-!)/rsACCHGGvvDvGtsr/]'-%.....#>;!{F^rzzsxABCBHGvtsr/{!-&.......+$;~FKtEEEDxI{;-$@..............+&=;)FzsACCCxsr]';#@................&#-;([JDEEEwz4);>%.........&=;_]^ztHHtszzxCGAsr])3,&...................................", +"..@*;~FryABCCBAxzzzzsyBACCBBt]*..+-(/stGLLGAzF)!;;;{/ztCCCCCAr;@..*-!)/sBBBCCCBCACCCGLGHCCAz/'*+.+#-!{/sABHGLLuEELGBBBBAyzrF~-*..@>;~)/rsssssGLEEDwxssssz^]';=@.+%-)/zACCCBxs/]~'){^sAvLDGxrF'$+.+#>;)/stCBCBBAsrrrzyBBBCABzF'*..#-~]rJtLLLLGHLuEuEGGCGLLLLtz],+..+&*=-;{FrsACGLLELvGBxz^]);>%+....+@#-~)/zxBBHLLLLvGAyz/{~-$&....+&*-;')]F/rzABCCCBCByz/{~;$@.......&,-~{IyLLEDyr)!-$@...............@%-;)/zAABCBts/]!,$#@..............@>-;{^sHLDDHz]!;,#.........@$-']/zstxszrrzsAyz^]~;$&...................................", +"..+&>-~{F^IKKKIQ2[2[^QrKrKzIF~&...$'([6wwwOQ|';,>,-;)][/IrIrQ]-+..@*,;)}rKKKJsKKKKKJwwwJIr/]~-&..+&*=;)]FQIJYwwwNwOJKr^^[F(!-*&...&>=-;'(1::[5ONSwP4:|((_!;,*@...@$-)FIKzKr^F(~---!(26wwYP[|~-%...+#=-)F^rIzIr^/]1]F^rKKzI/F~-@..@=-!{[PwwNwJJOSNNNwJwwwNwOK2~*.....&*>=-!(F[IPwNNwJPQ2]~;->*+.......+%$-;)}IKJwNNwOPI2(!->*@.......+#$,-;~)|F^KKJKKKQ}(!;>$#........&*>-!1%@.........+&=-;~((F1(___(]1](~;-$%+...................................", +"...&*-;(|}Q58O85<5<5<,>=-!(}[[QQQ2),+..&#=-~:QKIIPPOJJ8PJ6PKQ[:1~3*&...+*=-;'(|[<5888O888855<-9'~(|[OSRY5:('!;-,=*&@...+#-;|[&....@#-;_:}[[QQ[:(((}[Q[[[2:_,#+..@*-c_:5OwNwOO86M86OJOYNNSO7|!*.....+@*>,-~_}QP66O65<[1_'-=*&+........+#=-'(:Q5YNNM6Q:(!-=*@.........+%*=,;~(1Q6JJ8I[}1~;,>*&........&*==cd30abbf77h5imnqZqqqqifc9$&....+%#*==99afgqWUjib_09->$%&+.....%-cb4hh54fba9->3;afiqWqpfb0=@....&**3a|f44hkhfebbe7hk4feda-#....&>9afiqWUWWqjmgiipqWUWUWjgba*.....+&*=-9ab7hhm6m6lhkfdc9=*%.........+&*>9abfiqqUqifba;,$#@.........@&#=-;ab7ijWqjifbda;3=*%........@#>,cbijUWjgdc,*&..................@%*,9adbeijjgbc,$*@+..............@*,;bgpWWqibc9>#@..........+&%*>>,-=>,>==,-==>*#&+....................................", +"...&=3ae4kmqUTTTUTUUWZXXVl7ed,@..+*0d7pqUUql7:da'00db[kVVlVhkb,+.+%$,9_4hVXXZZWUUUUWnll41bac3=&..+@*>330d|e47kklVXoWWUUWWjga9=&.....+&#$>-cdfiqUUqpfdc3>$#%@.....@$3~ekVVVlke(c9399afpZWWpgea3%....@#>3ab44klV.....+%*,9abeklVVVVVVVl4edc3=#+.........@*=cab4iqUTqpebac,*&+........+@#>>3a:7hnZWUom4e|d03,=#@.......&*=9abiZUUoibc9=#+..................@*=90d:7moomfdc3>*+.............+#*30dgjWUqjfa9=*@..........++&%#*$$$$*$***$>$#%&+.....................................", +"..@$;~FryAHDEEEEEEEEELCCCAsrF-&..&-(^sGvEELHxszr///rsAACCBCAxr'&.+*,!)/stHHHGEEEEEEEGHAAsrF)'-%...#>;~{F^zsxytCCCBGLELEELGsQ{;#......+#>9')FztLEELAK^{~;=$&+.....%,~FzABCCBAsr]{~)]^stEELvyzF'*...+&=-!]^sxACCBAxsxACCBtysr{!,@..@=!{^stEEEEEELGBBADEEEEEEGAK]-...+%$-;~{/rxACHHHCCBCCxs^]);-&........+#,-~]^sGuEEEGK^]~;,*+........@*,;')FztAGLEEvHssz/F{~!,#.......&=;~FKAuEELyK]~-=%..................+#-;(F^stGvAs^F{!-$@...........+#-;~{rxGEEvAKF!;>%............+&#%#**###*##*==*%+..........+++++&&&&%@+++&&&&%@+.......", +"..&$;)FzxtHvLEELLELLGGCCAAzF)-@..@>)/KyvvGHHHAssrzzsxCCGGCCCsF;@+@*-'(^sBBHHCvvvvLvLvGCAsz/]!-#..+%-;{FrzssyxABABCCvGLLLLvyr]'*......+%>-;_]IyLEEEts/]~-,#&+.....%-)FzyCCCBtsr/]{{F^JHLLLHxrF'*....@#=;)/zsACCCAyyABCCCysr/{!*@..@>;{/sAvLvuELLHBBCHLELLLvvxz]=+..@*>-!)]rzyCHCHCCCCCCBsz/{!-*.........&=;!{^KtLEELts/]~->%+.......+#=-;'{^sBCGDLDvAxszsr/])!$+......+*-~{ItLvEDAr]~;=*&..................&$;;{/ryBCBsz/]);-#...........+#-;)FKyvDEutz]~;,#...............+@@@+.+++@@@&&@.........+@&*#%##$$$**#%##$$$**####+...", +"..@%>-~1//IPKJ6KI5I5..+&$-!)2IKJJKJKPQKPPOwJK[F|~;=%...%>;'F[Q/^/F/F^[QIIPJwNw8<1~-#.......@*=-;~:#&@......@=-~]QKzKrQ/F__~~(1[Pwww8Q|~-%.....@#=-!)]QPJJK^^QKJJK[])!->&...+#=-_:QPJJwwNwPIQIJwNwwJPP[{;%...@$=-;)(F[IJwJPIIIPJwKQ/|);-#.........&#=-;_:%@......................................+@&%#$$$*=*$$$$>$==*>>$**%#@...", +"..@$=-!(:2QI5I<[[[222|(_;;-,*%+..+%=3'|[QI5OOJ5Q[[QQ<6YSN6<:_;%...&*-;_:QIII-'(:}Q5OY8<[}Q6OOP<}2|'-$+........@%>-!_1*&.......+@%*,-!(:$>*@...", +"..@*=9ab44hiVih777ffeda933**%&....&*=9affhimqomiiikhipqqqjgf09#...%$90bfhmmmi44feffipjjphg7ba;%...%3ab4immh7eeeeeeffgpZWWjgbc3&......+%*=3;dfijUUjifac9>*&@......+&=;dekhhk74febbbbfgiqqoifd;,@......+%$>-0fippmiiimjpgbc3>$&+...+%$-cdfhlmmoophf77ioqqmmk7ea>&...&=-cdfgiijqqpg7fgiqqpmigfbc,@........&*=9abfgqTUqifd0->$#+.......@*$90dbgiqqjmhh74f7hmlihfa,+......+#*30bgqWWjied0->*&+...................@*>-0b4474eeda-$%..........+@%>-9abgqWWqiba-$%+.....................................+@*=,339c0aac0c;9000c0cc99,>#...", +".+%>;a:khVmnonmlhkk4ed09,>$#&@....@$>9ade4lmXoZqoqoXVmppppi4b0*+.+#-0b4inooonmh74efhmonoXopifa>+.+=9bfmoqqjmkhkkkkklmoWUWniea3%.....+&*>30begjWTUWph4dc3>$&+.....+#=90eklllllhkkkhkhlmnnXifa9=%......+@*=3abhXnopqooXmkea9=#%+....%*30b7VlXonnmhk4kVnoonXXlfa3&..@#30bfijqqqqnmlkkhmnoooqqphea*.......&*=9ab4ijqTTUjgeb03,*%+.....+&$-a17hmjWWomVllhhlmoZom7b9%......@&$3cfiqUUqmh4{a3>*@...................@#=3~b4klllk7|a3#+.........@#=-0dekmqWUqpea9=#@......................................#,;ad(b1ee2ee:e:eeee4ee:bdc>@..", +".+*;)/stCCHvDvvCCCtxz/('--#%+.....&=3~{FrsxBHDEEEuvGCCCCCAAsrF=+.&>!]rsADEEEGHAtszsxAHGDLvvts/;@.@-]rJGuEEuGCCCBACACCDEEEDyKF'*.....%=-~{/zyBvuEEEvttzF{;-$&+....+*-~]rxACBCBCCCCBBBBHHHCtz]~,&......+&>;~FryCGLEuEDGBxrF_;=%....+*-!]ryCCGHGBBBxsxBHBGGHCts/!*..&,)/zxuuEEEvHBtAttBHHvEuEvAsF-.....+&=-;)/zxADEEEEGBxz/)'-$&.....%>;~/stBCvEEDHCCBCCHGLvDHxr{*.....+%=3'{ItEEEEGAtz/{!->&..................@*-'{/zxACCByz/),#........+$-!{/zytGLEEEwz:~;=&.....................................@-)FrzsyxAABtxtyxtyBtBBByxz/~#..", +"..#;)/sACCCvLLGBCABsz/);-$#.......&=;!(/rsyBCvLELLLvBGBCCCAyr{,@+&-!]rxHGLLLGCCAszzsBBHvLLLGxr;@.@-{rsGuEELGCCBCCCCCHLEELGtr]!*....+%,;)]^stCGEEEuvCxz/{!->*@....+$-']rsAACACCBBBCCCCCCCCyr]',&.......@$-!{ryABvEELvHAs^{~-=&....+%-~]ryCCBCBCBysytBCCCBCCBzF!#..#-)/sAvEEELGBBAyytBBCvLEuDAs/-+....@#=-~{/sACDuEuLvByz/{!->#@...+#>;)^sBCCHLLLBCCCCBHLELDvyr)=......&*-!{IAvELLGCAsrF);-*&..................%=;)FzyBCCCBs/);%........&$;)]/zyCGLLEDtzF~-=@.....................................#;(/zyyABABCCBBABBABBCCABysr)>..", +"..&=;_FrKJJwwwJKKr^}{';=$&+.......@&$-;)12KJwwOOJOwwwwwwJJI[]!*..+*-!{2IJwwwJKI/F|]}^IPJwwOP[|-+.+$;([6YNNwPKKIKrKIKKJwwOP[(!>&....+&*>;~{FQKJwNNSJKQ/(~;=*%+.....&*,;)]/^rKzKzKKrIKKKKKI^|'-%.........@*,;':QKOwSwOKQF'--*&@.....&=-;{/QKsKKKI/}FFQzKsKKK/F~>@..@=-_1<6SNNwJKI^//^IKJJwNwOI[)$.....+&*,;!)2^IJwNNwOKQF_~-=#@.....&*,;)FQKKwwwOPKKzIKKJwww6[|;&......@#$-;1;_(}<6MMOPPJYwNSSYYO<}(-&..+%=;_1#&@.....@#=,;~(1[QIIIQQIQQQQ[[}|~-=%.........@&*,;(}[P8OO5[}_;=*%@......@#*-!|[QQIIQ[:((|}[QQIQQ2(;*+..&*3!12@......+%*>,_25OOPPQ[}('-,$%+..................+&#$-0|4OwY84_->&........@*>-'(1[QP68O6[d;,*&......................................&-!(1}[QQIIIQIQQQQIQIQQQQQ}:!*..", +"..&=9ab4hlllmVllhh41ac,$@+.........@#=3abfijWqqmmpZWUUWWWjied3&...&=cdb7kllmlhkfdaa_dbehhli7fd>+.+%>;dfg5i5hkllllhhhhh74fba9,#......+%>-0a:7hlm5mPlh7eda;,*%+.....+&*$,-c_e75hlhllhhh74ebd;=*&........+@&*>9ab4h5l5hfba9>#@.......+%*=cde47777ebdade777k77e0c*...+*3abfh5mmllh4:bb:7hlllmlh7ba*......@*,30be7kllmmmlk7fba;>*&.....@#*-cde47hlmllhlhlhlVllh7ba>........&*>9abhhmlhhkfba;=$#@..................+&%$=9dgpWWpga-*&........@*,90(e4hlVllhea3>*&+.....................................&3abe7h5lihl5ll5h5l5lk5khh4e0$..", +"..&=9d:kllVlVVVVVh7ed0,*&..........+%>c_bfhqWWoXXnZWWUWWWom7b3@...%>cde4hVlVVhked00c0b:7llkked,+.+%-9de7khllVllVlVVVlk4fbba9=&......@%$99dbklVVVVVVlk4ba9,$%+......+%*=-9abkhVVVlVlVhk4eba9,*@.........@%$=9abfkllh74|ac=*@.......@&*>9de4k7k4ebaaab47774eba3%+..+%3~bf4hVlVllkebbe4hVVVVVk7b0*......@#=;adekVVVVVVVlk4bac3*&.....@&$,0de7klVVVVVVVVVVVVVl7b0$+.......@*-9aehVVXVVkke(09=#@...................@%*>9agoqZjga9=&.......+%=,cd:4lVVVXVl7d0,$#+.....................................%9(eklVXXXXVVVVXVlVlVXVXVVl4d$..", +"..%,!]/zsAytyAAtAsz/]);=&..........@*-~{/rxtvvHABBHvLvLGvtJrF-&..+*-~]/rzyxtysz/])~){{^zsssz/{,+.@$9_{/rzssxxyAAtBBtysr^F{~;=#......@#>;'{/zsttBtAtxsrF{~;$%+.......&*--')FrsxAABBAAxsr/]);>#+.........@#>-;)F^zsssz^F)!-=%.......+&$;!{/rzzrr/]{)]F/rrrr^F);#...+*;(F^zsyAAtys^/F/rsxtAtysrF~=......@=-!{F^sxttAtAtyz^]{';=#+....@%=;'F/rsxAABABAABtAAAtyz/)=+.......&=-~FzxACCBCBxz/{~-=&...................@#=-!|IxvGxI{~,&.......@*-!{FzsACCCCBtsF);>*@.....................................*'/zxBCCHCBCBCCCCCCCCBCBCCBsF-..", +"..@$;')F/rrrrrrrr^F)~;-%+...........%,;~)FIzsssssxyAAByyyz/])-@..+&,;~){F/rrr/F)~;;;!){]F/F]{!=+..#-;~_{]/^rrrrrrrrrr^F{)!;,=&.......&$-;!)]/rrrrrrr/])!;=%+........+@*,-!)]/rrrrrrrr/F)~'-$@...........&#>;!){F/F/]{~!-*#@........@*,;'){]F]{)'!!!))){{)));,%+..@#-!))]//rrrr/]))){F/rrrr/])-@......+&>-!']/rrrrrrr/F))!;,$&+.....@&=-')]/^rrrrrrrrzrrrr^F);=........@%-;)^yABBBBtsrF{!,*@....................+*=;~F^rzrF);,&.......+%-;)F/sAAABAByr]!-=#@.....................................*'FrstAACCCCCCBCCCCACBCBCAtz{=+.", +"...@#=>-;;'''!'!;;;,=*#+.............@#=,-~(|(()()]]]F]:{)!-$%.....@#=>--;;';;--,>>>>=--;-;->*&...+%*,,--;;''!'~~~'!!;;-=**&+.........+%$=>-;;'''''!;-,##&+...........@&*=,-;;'!''''!;--==%+............+@&**>--;-;--==*++..........+&$=,---,-,>====->>->,=$%.....@#$>,-;;'';;;--->-;;;';!;-,%.........%*>=-;;'!~!'!;-->$*&+.........@#$--;;!!'~~~)~'~~'!;;-$&.........+*=-')]FF]]{)~;->%+......................@#==;!__';,=%.........+%*-;!)]F}F2]{~;=*@+......................................&=;~){]F}F2/2//[/[/[/2F2FF{)>@..", +"....@%***>>>,>>>=>$$#&+..............+@**=-;;!;;;;'~_'~!'c-=*&.....+@%**=>>=>=>*%&&%%$***=*=#&......&&*$===,=,,>=>,>==**#%+............+&#$*>>,,,>=>=*#%+...............&%%*>=,>,,>=>=*#&@+...............++&*#*$=***#%@.............+@%%***$%#**#&#%**##&%&+......+&%**$=====*=**$**>==,==$%@.........+&%$*$>,,>,=>=**#%@............+&%**=>,,=------==>=*#@...........@%>=--;;;;-,=**%+.......................+@#$=,-9->$&+..........@#*$,-;;;';;-,=*&+.......................................+#$>--;;!!!~~_~~_~_'__~!';;>#...", +".....+@##**$***$*#%@@+.................&**=,99->>-9;c9c33>>$%@.......+@%###$###%@..+@@%&%*##%+......++@%#**$***$***$*#&@@+..............+@&%**$**$*$$%@+.................@@&$*$$**$%%%&@+...................+@%&#**#&&@@...............@@&#&#&@+++..+@@+++..........+@&%#####&&&&@&&&##%#%%&+...........+@&%##$*$$*$$#&&@+..............+#**$*$*$*$******#&&+...........@&$*==,-,>>$*#%@+........................+&#$==>=#%@...........+@%#$*,,,-,,=$#&+........................................+%***=,,-33-339-9333,333-,>=#...", +".......+@&%&##%%%%&@...................@@%#**>**$*,>,,,>>=#&@.........@%%%&&%%&&+....@+@@@&&@.........+++@@&&%%&&@&@@@+.....................@&&##%&&&&@...................+++&&@&&@@@@+......................++@@@@@@@@..................++@+........++...............++..++++++.+.++.++.+...............++@&&@&@&&@&&@+.................+@&@%&&&%&&&%%&@@+.............+@%##*==*=*#%&+..........................@&%&###%&+..............+&&%*$=$**#%&@..........................................+@%***>$*=$========$=**=**%+...", +"..........+@&&@&&&@....................+.@%#&%%&%%#****%%#&+..........+&&%@&@@+@+....+....................++.++..............................+@&&&@@%&@.......................+++++.+.........................+.++.+..+.......................................................................................++@+@+.++....................++++++@@+++.+................@&%##%#*#%#&+++..........................+&&&&&@+..................++@%%*#%&@+.............................................+&%%**#*##%*%#*#*#**#*#%&....", +"..............+.....................................+..+..............+++++++..................................................................+..+++++.................................................................................................................................................................................................................+++.++++.................................+.+...........................+........................................................+....+.+.+...+..........", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +".................................................................................................................................................................................................................++++@+++.......................................................................................+...............................................................................................................................................................................................................", +".....................................................................++@%%&%@++..................................................................+@&&&&@@+....................................................+&%*#$#$*##&+.........................................++&&&&&@++..............................+&%%%&@+...........................+@&&@+.................+@&%&%&@+..........................++&%%%&%&&+............................................................................................................", +"..............+&&%&&@@.............................................@%#===>,>=*&@...............................................................+&*$=====*#%&@..............................................@&#*=,,3-3,3,,=*%@.....................................+&#*=,,3,=*#@+..........................+@#*===>=*%+......................+@#*=>=*##@+............@%#*=,3,=*#%@......................+@&*==,,3>==*%...........................................................................................................", +"...........+%$$,----=$&+..........................................%=-;;~){)));->%@...........................................................+%=,;;~){))!;->$&...........................................@%=-;')){{{]{{{)';,$@...................................+#--;!)){)~;;,*&+......................+@*>-;;'~~~;>$&+..................+&$-;!)))!;-,*&+........+%$,;!~){))!;-=&...................+@#--;)){{{{)~!-#@.........................................................................................................", +"...........%$--;!~'!-,*&+.........................................%>;~)]//^^])!->#@..........................................................%$-;!{F/^/F]);-=#..........................................+#=-!~{F//rrrr//F)~;=%...................................+#-!))F^/^F)!;-=%+.....................+*=;!){]FF])'-=%..................&$-;)]F//])!--=&+.......&*-;~{F^//F)!;-$%+................+&*--!)]^/r^^/F);-#&........................................................................................................", +"..........+%*--;!''!-,$#@.........................................&=;~(2[5*@.........................................+@*=>;!(F}[QPPP<&...................................+%-;)|}<<<[('-,$%+.....................@#=-!~1[;(4<;~}[5QQ}(!->*&+................+&#=-;(1Q#&@........................................................+%=,c(4565Q[}(;-*%.........................................+%#$-;(}[QI8OOYYO67(;=@...................................+$-~1[58O652~;,=#&....................+&#>;~:[5Y65e;-$%@...............+@%$-!f58O6<:~;>$%@......+&$-c(9;abe4fbd0-*#+........................................&>cb7ijqqjgfa;,$%&+.......................................................@#=3cbijomih4ea3*#........................................+&*==;ae4iimjqqoqqpgb9>&...................................@$9afgmqZoigbc->*%+....................&*=cdfipqqjga3>#@...............+%#$-agjqqjifdc,*&@.......@*-0fgjqjmi4ba->#%@...............+&=39abgjqqWWqjf09*%........................................................................................................", +".........&*,cdegmppi4b09=#@.....................+@@@+............+#30bflXnZnl4:ac,**&+...............................+.....................&*=,cabhmnXVVl4b03#+...................+++.................&%>9cd7hmnnonnnnnpiga3>#....................++@+...........&=9dekmnoXmkedc3>*%+...................#*9ab7lXoYmgdc=*@...............&%=,cd4mnZnl7:a9>$@......+%*30binnXXVk4ac,=#&@+..............@*,0ab7mnZWUWpgdc,#+......................+@++............................................................++...............", +".........*-']rsHDuEDxz/);=*@.............+&%%%*%$=*$#&+...........*;{/zxHHHHCxs/{~;->$%%@+..............+@&###*****$**$*%&@..............@%$,-')/KxACBCCCxr]'-&..........+%#*#***%#$*#@+............@#=-!{/ztGvELvGHHBBAsQ{'-*+.........+&%###**#*$*$$*##%%&+....#-)FrsCCGCHAyr]);--*%@+..............+@=-~FrxBCHBHs^{;,&.............&#=-;~]QyAHHBHxzF~-=%......+$,!(/sBHBHCAs/)';,=#$#%%@..........&$3'(/KxADLELDx[{;-&............+%###*#***$=$$****%%%@........+&###****#*#*#*##*&+..................+%##*$*=$#*%&+.........", +"........+#-!]rytDuEuAs/{;-=&...........&%#*=,--->----=#&@+.......+*;)/zyBBBCABsrF)';--,**%@+...........@%*==,----;-----,-=#%@..........@%#*,;;~]FryACCCCBAzF),&.......+&&#$>,-------->=#@..........@%*,;)]rxAGLELLHCAAysz/{;-#.......+&%##=>,-------------,*#@..+&-!FryACCBBBxs/{~!---=#&@+...........@#-;)FrtABCCAs/);-#+...........@%*,-!)FryCBCCAyzF);,%......+#-;)FzxBCCCByr]);;-->-,=*&.........@#-;)]rsAHLLLGyr]!-#+.........+%#$=,----;;----;----,*#&......+%#=>-,-,--------->=$%&@+..........@&%%$$----->--,,=#%&+......", +".........&*-!1[5wNNw52|!-*#@.........++&%*$>==,-=,,==$$$%@+.......@=;'][rKKKIr^}{~;-->,=*$%&..........+&*#=>=>,--;-----===*%@+.......+&%%*$=-;;~{][^IKKKK/]!-$+.....+@&###$=,>---->,>=**#+.........@%$=-;)][IJwNwwPI^^[F1);=*&......+@&#$$>>,>--;;----->==>$*@...@*-!(FIKzKKr^F|)';-,=*$#%@+..........@%*-;~]2rKKKIF(;,*&............@#$>-;~(][IKzKQF]~-=*&.......&*>-_]}^IKKK^F_;--,,=>>**&+.........+%$-;(1QPwNwO[_!-*@.........+&**=>==>-----;------,>$*@......@%#$=,,,,=>=>>>=>,,>**%&+........+@%%*#==>,,---,,,=$*%%@+.....", +"........+&*=3!b<6OY6<2('->%@.........+&%$>---;;;;;;;--=**%@.......&=-!_:QIIIQ[}:((~;;;->$*$#&+.......+&*$>>--;;;!!~~~;;;-,=*%@......+&%*$=--;;__(:}[QIIIQ[(!-#+.....@%#$*>---;;!!;;-;-,>*#@........@#=,-;_:[Q5OOO6<[}}1|(!-=*@.....+@%$**=,--;!!!~~!!;;;;--,$&...+#=;_|[QIIQQ[2:(_~!;--=$#%@..........&$>,;_|}[[IQ[:~-=*@...........@#*=-c!_(:}QIIQ[}(!->#@.......@#>,;(:}[[QQ[|~!;;;;;--,>*@..........&%=-;(}5MNR87_;=*&.........+*=,---;;;!'~!!!;!!!;;-,*#+.....@*=---;;3;-;;;;;;---=*$%&+.......+&#$$=,--;;'!;;;--,=$*%@.....", +".........&%$39afgipig7ea-*%@........+&#*=9;aaa|bbbdda0c,>#&+......&*,;de7hllhh74febb_aa;3==$%@.....+&#$==3;aa_bbbbbbbbdda0->*@.....+&#$=,-;adbbeef7khhllh7bc-%.....@%$*>,3;aadbbbbdda0;,=$*@......+%$,cadb47llmXmmhh74eeba9=#@.....@%*$>,3caddbbbbbbbbbddacc3#...+&*30b4hllhh744eebbda;3=$*&+........+&*=30db47hk74ba->*&+.........+&$>3cadbee7khh47fbd9=*@.......@&$=ca1e4kkh4bbda~dad0a03=&..........+&*>9cbiqWWjgdc,*&........@%>9;aadaddbbbbebbbbbbda;,=&....+#,30a~d_ddaaddbddda09==*%@......@%#*=,-;aa_dbbbbb~cc,>$%&.....", +".........@%*>90b7llVlh4bc3*@........@%>-cab1e444k774eeda3=*&......@*3cb4lVVVVlllkhkk74ed~093=%+....%*=,ca(be4444kkkkk7444bac,#+....%$,;'a(be47kkhhlllVVVVke_3*+...+%*,;'0_b4444k74k44:da03==%+....@*=cd1ekhVlXXnXXllhkk4ed;,*&....@%=99!ad1e4477kk7k7k7k74:d0$+...%>9_bklVlVllhkhkk74eba09,$@........&*>c0(e7kllll4edc,$%+.........@#>9a1e4[kkllhllkk4ba9=#+......@#*30b44kllVk4eee447ee:ba3$+..........&*,9afmqWWogbc9>%+.......&=9_be44747khhkkkk77474eda3$+...%=9_be47744e4474744e|dac3=*&....+%*,;'a_be4447kk74eedac3,#&....", +".........@*=-;~/KxtAABsr{!,%.......&*,;)/rsxAAHBHBBAAAs/{!--%+...+#-'{rxBCCBHHHHHHHBBAAyzr/{!,&...@$;!{/rsxyBCCBBBBCHAHAByzF)-%...&>;~FrzsAtCBHHBHHHHGBCCtzF'>+...&,;)/rzsxABCBCCBBBtxsr/]~;>&....%-']rsxACGHHvvvGHCCBBts^{;,*...+#-;{/rzsAACBCBBCCHACHABtyr];+..+$;)FzxCCCHHGHHHHABBAsz^F)!>&......&$-!{/rytCCHHHts^{;-*&........@#-;{^zxtACACHHHHHAAz/);>&......%=-;{^stACBCByxsyAACAysz/);%..........#=;~]KtEEELyI{!-$@......@=;{/zytBBCBHBCHBCBCCBAAAsr]!#..+>!]rsxABABtAyABABAAAxzrF]~-$+..+%-;)FrzsytBBBBBACCAysz/]);>&...", +".........+&*,-')]rzsyxs/);,#+......&=-~]zytBHGGLGLLLGLBs/)!-$&....&>!{^sACCHvvLLvGLLLLGCBts/);*...%-!)/sxBCHvHCCCBGvLLELuGs^{;*..+%-;]rytBCvGLLvLLLLLGHBCAzF),@..+$-!]rytBCvvHCBCBvDGCBAsr]~-*+..+*-!FzxCCHHLLLLLLGBCBCAxr{'-#+...*-~FzyABBvGCCCBHvLLvLLGHts/-@..+$;)FzyCCCGLLLGLGLLGHAyzrF);*......+=-'(/zxBCHvLLHxr{!-=@........+#-;]rxABCCCBGLvLvGHs/);>#......@*-;{rsyBCCCBBABCHLvHBAxr{;*.........+&>;~]IAvEuvyr]!-=&......@=']zyCHLLLLGLLGLvLLLLLGGBxr{>+.+='FzxCGLLGCBCCCCCCvHHAsr/]!>&..+*-!{rytBCGGHCBCCHGGCBtsr]'-#...", +"...........+%*=-;'~{]F]);=#@.......@*>-~FQKKJwwNwNwNwwOQ|!->*&....@$=;)/IKKJwNwNNNwNNwwJKK/F!-%..+%=-~F^KKJwwwJJKJwwNwNNw8<|~-&...&=-)F^KKJwNwNwNwNNNwJJKIF'-*...+%=;)2^IKJwwwJsKJwwwJJz/}';,%....&=-~F^IKJwwNuNwwJJzKzr/|;-*&....%>;(/IKKwwwwJzJJwNwNNNwJK[)-+...%>;_]QKJJwwNNNNwNNwwPI}F(;,#.......%>-;)]/IKwONNOI|;-*%+.........@$-;{/rIzKJJwwNNwwOQ1;,*&.......&$,;)F/^IzJKrQ/PJwwJPr2{!=#..........@#>-':5wNwOQ|'-$#+......+#-)FQJwNwNNNNNNwNNwNwNwwPIF~$..+*;)2IJwNNwJsKsKsPwwwJK^}]~-=@..+%=;)FQrPJwwwJsKJwwwJKKr1);,%...", +"............+@**>--'_(_;,*&+.......@#*-~1[QPJwwwNNNNNRO41';-*@....@%>-~:[QI6ONNNwNNwNNYPK<[1~-%...#=;_1[%...#,9_1[QPPwNNNNNNNNSOJPP[|!-%+...#=;_1QIPJYSO8PKPOSOOII[:_;,#+...@*='(}QIJwNNNNNROKIQQ[}(;=#@...+#-;(}[IKJYwJPKP8RNNNNNw8Q}_=+...&>-'_}QIJONNNwNwNNNR6Q[1(!,%.......&#--~(}QI8SNNR<1;,*&..........@*=;(}2QQIKJwwwNNSO7_->*@......+@*=;_(:}Q=3c_a9>$&........&*=9de4hinqZWZZZWZqogeac9>&....@#=3a:7lVmoWUWWqZWqqommlhfd3%...%,;dfhlmXqqqmlhmpqUTTTTjgb0>&...%,;bfkimpqWZqZoWWUWjXmk7bc-#...+#-cbfkmmooWqpmlmoqopmlh7d;,%....@*=;de7hpoWWUUUqpi<77e1a9=%@...@*-ab7himqqqqphmpqUUTUWqphf0=....@*30d4hlmnqWWWZWqWWqmh7f:a3#.......&*,30df7ipWTUqgbc9$%+.........@$=9d1447hlmoZqZWWjgd3>*&......+@#=3ab:4hiVihhiijqqp77bd9>&..........+&*,cbgqWqjgd;,*&........%>cb4mnqWUUUUUUWWUUUUUZjmgea>..+%-0b7pqWUWqnmmmlmmoqoi47eb0,@...%>9bfkimpoqpmhlmpqopmlh4dc,#...", +"..............+@#*>999;3=%@........@$3'dekklXnooZooZZWqm7edc3#+...@*>c04hVXnnqZooppnXooonomhea>+.+$9aehmnonZopmhhhmnqTTTTqiea9#..+$9dfhXnonoononnooZooXVVhe_3*+...$9dflpnonooXXmlVXnoooonkfd9*+...@#>cabfklnooZWqqmhk4eeda3>*+...%,cb7innooooXmllmoZZZZZnmked>+...%=3a1kVlXnXZononnnooXVlh4:c$+......@*=9cd:7kjqUTUifa3=%+.........+&>30dbe4khlpnooZWqib0,*&+.....+&*>0_e7klnooXpnjqoolkeba3=&..........@$>9afiqUWqgb03=%.......+#3ab7lnZZZZZZZZZZZZZZZoXVVkb,+.+#,ab7loZZZonXmmllmnnnVVkked,#..+$cdeiXXonZoXlhklXnoonomifdc=+..", +"...............+%#>---;-=*@........%,!]/zxtBCCCCCHCDLLvAysrF',&..+&>;)/sAHHvGvCHAyyAHBHGLDDtsF;+.+-{^sGDDLvGHBtxxsAAvLuuuuxKF;$..+-(rsGvLLvHHAtxtCCvCHHCBAzF'=+..+-]rxGvEEDGHHAAABCBHGvDDGxr]-+...%*,;)FrsxHHGGvGCAssr/F{~;-$+...$~FztHDvDvHHtysAAHHGGGHCAs^(-@..+*-~]zABCBCCBHBAtAHBHHBCCxzF-+......@=-;~{/zsGEuEuwz{'-=&.........+%=-'){F^rzsytBCvLvtI]'-*+.....&*-;{^sxBGvLLEvLDvHAys^]);>&..........@$;)FztEEEvxI{'-*.......&=;{FzxCHBGHGHHvCHCGvGGHHBCyr;@.+=!{/ztHHHGBHCCtxtAHHCHCBAs^)$..@-{rsHDEDGCHBxysAACCGvLDHyr];#..", +".................@%#$**%#@+.......+#-;]^zsyBCCCCCCCCLLLHxszF)-%...#>;)/stCBCCCCtssssxACLLELGs^-@.+-(/sHLLLvCttzzzzsytGEuEGy/{!*+.@-]rxHLELvBBysssxACCCBCCtzF)-@..&-{IyGLLLLHCBBABCCCCLLEuGyr{-@...@*,;~{/rsACBCCCCyz/F])!;-$@...+$'FzAvLLLGCtyszsyACCGCCCxzF),+...%;)]zyCCCCCCBxssyxBBCCCCAz]-@......+%$-;){^sAvEDvtr]~-=&..........+%>-;!){F/rzsytGLLyz])-#@.....+$-!)^zyBHLLEEEELGAAsr/{!-$@..........&=-~}KAvEEvyr]'-#+......+=;~{rtABCCBCCCCBCCCCCBCCCCt^;@.+=;)]rtACBCCCBAysstABBCCCBy/)=+.@-]rxGLELvBAxszzsytCvEDLGyrF~*..", +"...................+@.+............@=-'{F^/KKJJJsKJJwwRPQ2]_-=@...@$>;~/IKKKKrI/2]1F2[IOwww6}(-...$'([6wNwJQ}F{)_){:}QP65<2_;=@..+=!([6wNwJQ[2]1]2^rKKKKKQF)-*...@>'1<8NNNwwJJPJKJOJwwNNR8[|'=.....@%*-;!1F^IrKzKI/:);;,=*%&.....#-_:-;(:5ONwO5|!->*+...........@&##=>--'_(]2[POO<1!->%.......@=-;)F[IJwNNNNNw8Q}|_;;,*%............%>-~:5wNwO<('-=@........&=-'(2IJJsKKKKKKzKKzKKzIKrQ],+..%=-'(/IKsKKrI/F]}F^IrKKKr[);%..+=;1[6wNwPI2]]|{]2}Q8www6Q|~-%..", +"...................................&$,;(2QIKPOOP6IKPYSM5[:_!-*@...@%=-~1[IIQQ[21((((|:48RSO5:'=+.+*9_15YNR841_~;;-;~_|1}}1_;,*&...*c(}POSY541(__(_:[[IIIQ[|!3*+..+=;(g8NSNNMYYRRRSSSSNNNR64(;*+.....@#*=;~1}QQQQQ[1_;-=*%@+......%-;(78SNY8<[}1|1}QIIKIIQ2(;,%....%$>;(1QIK..+&*-;(:QIIIQ[}1_(((:2[III[:~-%..+*0d}6YSY52((~~~~_1[5RSY84(;-&..", +"..................................+&*-0b4hlmoWWqnmmpqWqigfba3$@...@#=-ab7hlhg4fbbaaddbgpqWqie0>..+$9depqWqpfbaac93990dbbbd0c,*@..+$cdfpqWqpfbbdaddb47hllh7ba,#...+=cbgjTTTTUUUUTTTTUUTTTTjgbc=......@%$>9a174hhkk4ba;,$#&+.......@=;dgpqWqnih4fffghmpmmlhfd;=#....&$,;d4hlli4febaadbe4hhlh7b0*.........@%*=3;bgjWWqgb0-=%@...............+@%#*,;0bfpqjgd9,*&......+@*=30bf7pqTTTTTqigba0-$#&@...........&*>90bgqUWjgdc>*&+.......@$=;0ehlmhhk7khhhhhhhh7hhhfd>...&*,;0e7hlhh4:bdadb:47hhlhea3%..+*cdgiqWqifbaacccadfiqWqpfbc,&..", +"..................................+#=9~ehVXXZWWZonnoWUWph7:dc=&...%*30d4lVVXVk7e:bbb|fhpWWZpgd,+.+=0b7pWUWogeba09-3cadb:bbac3$&..+=abgjZWZpkebdbb:4klVXXlhe_9*+..@,0fgqTTTTUUTUTTTTTTTTTTqgb0=+.....@%*,cdekllVllked9,=$%@.......&>3a7poqZonpmmimmnZZnnXVkb03%....&=3aekVVlVk4e|bbbe4klVVlh4a>@........+#$39abioWUqpfdc,*&+...............@&$>,cab7pZqib09$&+.....@#$3'be7hnWTTTTUWp74b_93=%@..........+&*,9afiqWWoieac>*@.......&*,cd4hVlVlllllVVVVhllllllkb,+..@*3ab4lVVVhk4ebdb:fkkVVVl7b9*..+=0bhnWWWphbbda_ade4pZWWpiba3#..", +"..................................%-;)^stHvvDvDvBCHvLEEGBxz^{;%...*-~]rsAHHGHCAyzrrrssHGEELGs/;+.@,]QsHvELGtsr/])!~){/rzrrF)!-%..@-FryGEEEGAszrrrsxtACHGHts/)=+..@-2IwuEEEEEELELEEEEEEEEuuJr:-@.....%=-;(^sACBCBCts^{'-->#@......#9~FrAHHHGDDuvDEDEDDGGGHyr{!>+..+*-']zxCCCCAtsz^/rzstACCCts/;#........@=-!(/zyEEEDtK^]~;=#+.............+&*=;!{/rsHDvtKF'3$@.....&*-;{^stAGLEEEEEEGAtz/{!;=%+........+#>-'{[stEEEvxK/{;-=&.....+%-;{/sACCCCCCBBCBCCCBCCBCCtr;@.+#-)FrxACCBBBxzr^rzxABCCBCyr(>..@-FIxvLELHysr^^/^rzxGLEEGyKF'$..", +".................................+%-~]rxHvLLvGHCCCCHLELGCys/);*+.+#;)FrsACBCBCBxsrrzsxCvELuGs/;@.@-{rJGLELLAyz^])~))F/rzzrF{~;*..&-{IsHLuLvAyszzzsyBCCBHCAsF)-@..@;]rxuEEELvvBHCHCGGvLDvGts/(-@....+%>-!)/sAABCBCAs/])!-=*%&.....%,;{^sABCGvLLDuvELLvvHCBtr]'=+..+*;)FzyCCBCByzrr^/zsACBCBBs/~*.......@#=;~{/KtGDEEAs/]~;-=*+...........@&*=-;){/zJtGDyr]~-*&.....&=-'{rstCBGvLLvvvvGCxrF)'-$@.......@&$-;~]^JALEuvAzF{!;,*&.....*-;)^sBCBCCAACCCCCCACBCCACxz;@.+#;)FryBCCCCtysrr/zttBCCCBtr{=+.+-{ryGLEEvAxzr^^rzyAvLLLGxr]'*..", +".................................+&=-~1QJwwwJI[^[/QIJOwPQ[F_!,&...&=;'{/QKKKKzK^FF]2^IPwwNwP}(>+.+$;([PMNNOIQ}|)';'!)1F2]]_~;=&..+>;1[8wNNOII/2FFFrKzKKKKQF_;*+..+>~:QJwNNwJKKIIIQIQIIIIKQ:);*......@*=-!)FIrKKKrIF|~;->$$&@.....+#=-'{F}QI8886O8JOOJJKKK^]!-%...+%=-~]/KKKrQ/F1){(F/QrKKKr[);%......+&**,;!(2-;'_2PwNNO<}_!-,=*@.....&=-;_/KKJKKrrIzKKzKzIrKzKK/F,+.+&,;)][rKJKK^2F(((]F^IKzKK^]'*..+=;1[8wNNOPQ/](||}[POwNwJQ|',&..", +"..................................&$-;148wSO54}::1}[6OO<[}|(;-%...%=-'_:[IIPI-;(:[QQQQQ[2(~;--=*#&......@%>,3!(|2[[<[[[[<-;'~1$#@.........@%$$$=>-;~(1[6M67|;-*@......&*$,;(1}[QIPPI>-;_(:$#+....%==;_}[IPP+..&*-;(1QIPP<[2()_((}}[QIIQ2|;$+..$;_28YNYO<[}:1(1:[QORSM52(;$+..", +"..................................&*9cbgpWWqpgf7ffggjqqphffbc,%...%=-0dfhmmpXmihffffghmoqWqib0$...%,abiqZqnmh7ffbbbbff7f7fbba-@...*9afiqqWjikffff7hmmnmmi4fdc>+..+$3afpqWqqmihg77g7ghhilhfbc,#.....&*=,;abf7hhlikh7bbdc09,*#+.....+&*=-30dbffff7777hhhlhhedc=&....#>;db7ilihgffb(dbef7hiVihfd9*....+@**,-0adbggqTTqigbbaa;3$&........+%**,,9;cadbfgmjjgd;,$&......@*=9abf7hhlmikhhmoWqigfbd09%......@%*-;cabffiqTUqifb_ac9>*@....&=-0dfhlmmlh4hhhhhhhhh4hhhhb,+..%=;abehmmmi47bbddbf7hilik7ba=...%3afiqqqjmg4feff7gipWqqpfd9$+..", +"..................................&=3aegoWUWommimmmoqWWopmi7d;%...%,0b4hmnoZZooppmimmpooZqjif0$+.+#3aeiXoZoXmmiiiiiiiiimmhkeb9#..+%;aeinZoonmmmiimpoZooonmked,+..+%9afioZZooonpppmXmmXnnmhea9*+...@*=9_beklmjononpmih74eda3*&+.....@#$,c0de4k+....@&=3_be7gijqWTTqjih74eda,#+...%,0d4hmoonmVlllVlnnnnVVVnpm49+.+#,ab4hmnonnmiheee7lmXooonh4b,@.+#3afmnZZonXmliiimXXoqZqifac*+..", +"..................................&,']ryDuEEuvLDuvvuEEEEDDHxI(>+.+$!]zABDDEEEuLEuvuDEvDGGGAs[)=..+>;{/yBHHvLvLDDGLvGLDLDGGByr{=..@=~{rsCHHGLLuvvvDDEEEvEEvAs^'&..@>!{rxHHDvvEEDuDuDDDDDvDAsF'=+..+%-!]rxAHGLEuEuEDDDGHtAs^);$@.....&=-'{/rsxtCACCCCCCCCBBs^);$+..@-)^xtvuLuEuvGxssxtDvEEuEvty/;+...%,;)^stBHGuuuEuEuuDHAAxr{-#.....+$-']/rzsyxBBBACHHCs/);-#+....+$;'FsADLDGHCAtxtAEuEEEGGtxz~%...+#-;{rytBvuuuuEuEuEvABts/)-%..+=;FrtHvEELvHCCCCCGvuvGHHvDDJ(@.@>'/ztHDuEEvDGtssstvDuEuEEGtz~@.+>!{rxBHBGLvuvvvDvuDvHGHtr1~,@..", +"..................................@>;{^sGvuEuDuLDuLLEEuLGuDyr{-..+>;FrxHGLLELuDDLDDLLLGHCByzF'$+.+*;)FzyACHvLLLLvuDLGuDuDvtxr{>+.+$;)FsxBCGGDLLuLuLLLLLuuvBxr;&..+*;)/sxACHvDvLDLvuLuDDDutzF'>@...*-~]zyBBHvLLEEuuuDGCCAyr);-%.....%=-~]/zsBBBCCBBCCCBCBxzF';*+..+>)rsAGuEuvuDGyssyAvDLvuuDHxr-+..+#>;)^xBBHvEuEEuEEDGHCAyz]!$+....&$;)FrsxAABCBCCCBCts/);>#+....@$;~}zwvGvHCBAtssxvEuuvuDGAz]$...@$-']zxBAGvEEuuEEuvvHCAyr)-*..+$~FzAAGLLLGCBCCCBGvvvHCHvvvx)&.+>)/sAHGDLuuLLAxsxAGvvuEuLHAr~&.@*;)/ztACHGLLLuLuDLvvCCxs/);$@..", +"...................................%=-!|[5P666J68O666JO86P<4(;%...#,!1}<588O666686668PIQ[2]_;=&...&*-;):/[<56P8P6K8668P8P<[:~-%...&$-;)]}[I6686JO8O8O6O6P,;~|/^rKzKzKzKKKr^/F);,=@...+*-(:Q<6O6J65<[::2Q566J665Q})>....@$>;)F[[IP6OOY6O66PII/F(;=%.....&*>;_F///rIzKKKKIQ[|!,$#@......&>-'1QP6PII^/F::4<668OPPI[];&....&$-!{F[Q<66OYY6OO8PIQ/]);#@..+%>!12<6686IIQ^QQQIPPPQQIPP<4;+..%-'][QP66J6PP[}:2[-;'_1:2}}}[[[[}[}2:1(;-=@...+&*,-'_(|22[[4[[[[[[4[[:(';*+...@#*--;_(|}}[[[4[[[}}111_;,*&+...@#=-!(|:}42[<[[[[[}:1(~;,*&.....@#=-;_1}QQQII;(1[[QIIIIIIQ[:(!-=%@.......&*>-!b}2}21(__~__:2}}[4[:_!>@....@**-;_(|}}}}727[4[21|(~;=#+...@$-!(:2[[[2:11|1}}4}}2::}}}_,@..&=-!(:}[[[[4}:___|2[4[[[}:(;=...@#*,-;__|:}[}[[[[}e1(_;9-*#@...", +"...................................+%*,300dbbbbfbfbbbbbfbba0,*&...&*,90abbbbbfbbebbbbbda9c-=*&....+&*=,,390dbbbbbbbfbbbdda;-=*+...+&*>=390abbbbfbfbbbbebbba9-#.....&#>,33cadbbbfbfbbbddaac3=#@....@%>-9addbbfbffffbfbbda;9=&+.....@#=3ade45llhllmmlh7edcc,$*@.....&=3cabbffbbbd000adbbfbbbbd0-&....@#$$3c0adbbb1fbfbebbda09=*@.....@*>cdf7hhhhllllk4ba;>$&+.......@%$-;abbbbdaaa9c0abbbfbbba;*.....+#$,30addbbbbfbebbbbdac3$%....@*,cadbbfbbb(b(dbbebbbbbbbba=..+@*,c0dbbbffbbd000abbbfbbbda9#....&#$,-3cadbbbbbbbbba0;3=>#&....", +"....................................&#*,ca_dbbbb:bebfb:bbba9-=&...@$>3;adb1bb(bb|b:bbdac9,=$*@.....+&#*,-9c0bbebe:b1bbdacc3,=%+....+%**>-cadbbb:bbb:b|b:bdac3#+....@##$,,30ad:b:bbebbdaac3,$#@....@%*>c0ab1b:eeeef1bbaa09,*%......@*>c_b4kVVllXXXXml4ed09>*%+.....%*,cadbeef1ba'9c0ab|be:bda0>&....@&%>,9caab1bbb:b:bbdac;3=%+.....&*=0b43;0adbb:b:bbebbaa0;,=&...+&*>-cab1be1dbddddbb:bdddb:dc$+..@*=30adb:bb:ba0330db:b1bd_0,%....@%*$=399abbfbbb1b003,=>*%+....", +"....................................&$=3;_(]//[//[///[///F{~-$@...@$-;~(]2/2F]F}////2]_';3,*%+.....+&#$>-;!)]2//}^//2F:{_';->%......&#*--;)]/[////[//}///](';=@....+&#$33;~(]/}////2F1_~';-=#+....@#,-'))F2/^/rzr^^/F{)~!;-%+....+&=-)F^sxBCCCABCBAAsr]~;-*#@.....%-3!){F/^^/](~';~{F//}//F)!,&....+%#-;'~){F2^/[F[//F{)~!;-#+....+#-;)/sxAABCCBABxzF)!-*&........@%*-!{F/F])_!;;;'_]/^^^F]);*......@*>-!~)]F[/[/////F{)!';>%...+%=-;~(F^^^/F]]]]]F//F{{{]F:)=...@#>;~{F}//[FF{)'~)F}/[FF()~;#.....&#>--;_)]F[//[/]{';;-$#&+....", +".....................................&$=-;!))))_){{{{{()))!;=%+...+&*,;!))))))){)()))~;--,#&..........@*>>;;~))){){){))!;;-=%+.......+&*$-;))))_({))){){)~!;-$@.....++%*>,;!)){({){))~;;--$&+......@#--;!))|)]]{F]]{)~;;-$%%+.....&*,;)]/zsysxysyssr/]);,*&+......+#,-!))(]{)~!;;;;!_{){)));;*+......@%=-;!~){){{{_{))~';->#@......%$-~]^zzsyyxyyzrF{~-=#+.........@*>-~)))~!;----;'~)(]){);,%.......@#>,;!~)){){({)))!;;,$%+....&#=-;~){{)))~'~!))())'!!)))-#+...@*-;!))){)_)!;;;!)_))))~;;$@......+&%$,-!))_){)~);-=#*%@......", +"......................................+@@&#==-,->-=,>--===##@.......+@#*==,==>=>->>==>*%&&+.............@&#$$>>>>=>>>$**%&&+...........@@@&$=,,>=>,>=>====#&@+.........++@%#$=>=>$>$$$%%%&@+........+%***>>->--,-,->>=##&++........@#=>-;~)))))))));;,=*&+..........&#$=,,>>==>****==>=>==>*%+........+@@##==>==-,>>>===#&+........+&*=--'~))))))';-,=*&@...........+%*====>*$$*#**>=>,->$*%%+.........&#*==>>->-,-=>>=**%+......+@@#*==->->=>====>>>,===$**&......+&**=>>,,,==****>>->===##@...........@##=,-$=$**%@@..........", +"........................................++@@@@&@&&%@%&&%@@@+..........++@&@&&&&&&%&&@@+....................+@&%&&&%&@++...................++&@%%%%%%&&&@&@@+..............+@@@&&&%&@+.+.............+++&&&%&&&&%%%&&%&@+.............@%%%**$====>=**%%&@.............+@&&&%&&@@@@@&&&%&@&@@+............+.+@@&&&@&%&@&@@@............@&&%%##$$***$#%&&@..............++@&%&@@@+@@@&&##%%%@++............+&&&#%%##*%#&&@&+..........+.@&%&*#%%%%%&&&&@&%%@@+++.......++@&%%&%@&@@@&&@&@&&&@@+..............@@@@%@+++.............", +"..............................................+...+..........................+........................................................................+...........................+........................+++...++...................++..++++++++++......................+....................................+...+...................++++..+++.+++.............................+@@@@@++.................+..+@&@&&@@@.+..............+++@&&@@+@++.+...+....................................................+...................", +".....................................................................................................................................................................................................................................................................................................................................................................................++............................+............................................................................................................", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +".................................................................................................................................................................................................................................................................................................................................................................................+@@++..........................................................................................................................................", +".............................................................................................................................................................................................................................................................................................................................................................................+@&#*$==#&@.....................+%#***%@...................+@#***##@+..............................................................................", +"............................................................................................................................................................................................................................................................................................................................................................................@%%*$3c99>=*&+.................+&#=3999=*&.................@%#=9c;,=#%@@............................................................................", +"........................................................................................................................................+@@&%#&@+..........................................................................................................................................................................................................................&**>,30dda9>=*%+...............@%#,;addac,*%...............&*=>3cdd093=>*%+..........................................................................", +"......................................................................................................................................+%*=>----=#%&@.....................................................................................................................................................................................................................+%>-!~)]Frr/)';,#&..............+#-;)]/rr/{!-=@.............@*-;')Frr/])~;->%@.........................................................................", +".....................................................................................................................................@&*>-;;!'!--$*%&....................................................................................................................................................................................................................%>-!)]F/rzr/)!--*@.............+%$-!]rzssr]~;-#.............@*--!)/zsz/F])!--#@........................................................................", +".....................................................................................................................................@%$,-;;'~'-,$%&@...................................................................................................................................................................................................................@#=,;~|F}}/}(;-=*&...............&*>-_2IPIQ|;-*%..............&*>,;)F2/}F|);,*#@........................................................................", +"....................................................................................................................................+&$=-;!~(1(!-*$%@...................................................................................................................................................................................................................@#*,'(}[[[}:~;=#&+...............&*=9_48OO5:!,=%+.............+#*=-_|[[[[}1~->#&........................................................................", +"..........++@++................................+@++@@@++@++..............++++...................................++++...............+@#>-ca(beeea;,$*#&++................++..............................................................................................+@@+++.....+@@++..................................................+@+++@@++++++++...............%$>;dfhhhhfea9>$&+...............@$>9dgqqqjea-=%+..............&$=3abfhkihed;=*%+.......................................................................", +"......+@&#***######*##&&@+...............+&&%###********%##&+..........@&%*#&##%*#%#%%%&&@+................+@&&##*##%#&&&@+........+%*30d:44kkkedcc,=*#&@............+@%%#%@+.....+@&%%@+............+@%#%#%%%%&&%&&%##*%#%%@........@@&%%%#&&@++...+@&%%%%@.........+@%#%**#&&&&&%#%**#&+..........+@&%%%&&&@++..+&%%%%%%&+...........+@&#%************%@+............+&=3abhmonV7:d;=*&+..............+%*,0biqWWqgdc>*&.............@%#>3d:7innXifa9>*@.......................................................................", +"...+%%=----;-;;;;;-;-;->$#%&@+........+%##=---;-;;-;-;---,,$#+.......@%*=,-;------;-;-;--$*@+............@%$=,--;-;;-;--->$%@......&$-~]rzytBBBsr/{)!;-=$%+.......+%*=>----$##%%##$----,=*%%@.....+&%$>-;>-;---->--,--;;--;->&.....@%$=,--;-->=*=###$-------*@.....&*$$--;----,->---;--;-$*@+....+%%$>------==#$#$$>-------$%+.......@&#>---;-;;;;;;-;--->*&+.........+%$;'FstEvvHs^]);>%..............+&=-'1KtEEEDxI{!-%.............&*=;!{^stDDDGs/)3,%.......................................................................", +"...@#,-;!!~~~'))''~!!!!--,>*#@.......@#*=-;;!!!~'~~~~'~'!;;->%.....+%$>--;''!!!!!!!!!!!!;-,=%+.........@%*>,-;;''~!!!!!;;;--=&.....#,;)FzsyBBCAyz/F{)!;-,=#%+....@*--;;;;;;-->,>$---;;;;--,*#&....@#=-;;!~'!~!!;;;;;!!!!~!!;-=@...&$>--;!!!!;--->----;;!!!;;,#+...&$>-;;''~'';;;;;;'!~';;-==#+...@$,-;!!!!;;---=-,-;;!!!;;--=&......@#>--;'''~'~))~~~''!;--=#@.........%,-~FJAvuvHs/{';=&..............+*=-!{IAGELGyr]!-#+............+&=-')FJALLLGs/);=#.......................................................................", +"..+%*=-;'!!~~~'~'~~'';;--=$$%@......+&*$>>-;;'!!'~!!~~~'!';-,#.....+%*,-;;;;;;;;;;!''';;--=*%+.........@#$>,-;!!!~!';';;;--=*&....+%*,;)F/QrKzK^/:{)~!;-=$*%@....@*=,-;;;;-->==>*>--;;;;-=>$%@....@#*,;;!'!!'';;--;;!!'!'!!;-$@...@*>,--!'';;-->==>>--;!!!;;-*@...&*=,-;!!!!;;---;!''!!!;-=$#@...&*=,-;';!;-->===>,-;!!!!;--=&......&#=,-;;!!~~~)_~'~!;;;-,>*@.........@*,-~[6www5}_;-=%+..............+&#>-'15wNwO<('-=&..............+&*,;!2IOwwO[_;>*@.......................................................................", +"..+%=-;_(1111|({:1:1(((';--=*%+.....&$*,-;;~((1::111({:1{|(!;*@...+#*>-;~_)|{1)__)(||(((~;->#@.........&*=--!~(1{::|)((__~;->%+...@%>-c|}<=--c_|)(~!;,*#@....&*>;!(b|{b1||_!!!_b{1b1{1_'-#..+&*,;;_(b||(~;;---;;~(1b1)~;-&..+&=-!~(b:1b1)~!!_)|b:1b|~;=#@..+%,;;~1b11(_;-->,-;~({11b(!;,#.....@%*>-!~((1:1e1e:11{1(~!;=*@.........&*,3ae5MSY5:~-=#&...............+@#*-;(3;aabe4gimppikgiijiigbd9&...@#=,adf7giiiffefgihig4edc3*&........@#>3cabe4iijpig7fffeba-*@...@#>9aeijqqqqqjjjjppigba9,*%+...%,cbegipied03-3cafgig7fba;=*@...+#,;dbgijppijgebdbeipppijigbc*..+#>cdegimiigebaa0adbegipiigbc*..+#=cbgimjpiigbbbfgpijmiiea9*@..+%3abgiipigebac9;0degiijigba;*....@%$=cabf7gijpjpjjjpig7fbd9>@........+#*-cafpqUqiea3=%@...............+&#$9cbijWWjgd9=*&................%=3cbiqUqjgdc-$@...............+@&#&%###&%&@&@@@++.....................................", +"..&,cb4ioqWZomXXoWWonVlkeeba9=%....%=3cde47hVXoqWZnmVXqWWomhe0*..+@*>c(7klmoqomkhhmqZnmVh7bc3#+......&*=3cd:4hmoZWWoXVVllkkea,&...&*3abioWWUTTUUWWWWWqmgba9>%@...#cb4ioqqqmebda0dfiqopVhkedc=%+...*9dfkmqqWWWqoi777iqqWWWWomhb,+.+*cdehpqWqomh7eb{b4hhpqWWjmhb,+.+*3agjqqWWWoi7f7ijqWWUWjib9,#...$_ehmpWWonh7ed_db4hmoqWnphea>+...@*=-aeklVoZqWWWqWWWqnVhkea>#........@*,9abgjWUUpfa9>$@................@*=90fiZWWogbc3$%+..............+#=9afjqUUqibac>#+...........+&&#*==>>===>=*==>=**#&....................................", +".+$!]zxHGDEEvCGDDEDvDBCAtsz/{;$...%,;{/rsxACCGDEEEvGGvELEDHts/-..+*-;]rtCCHvDvHCCCGDEGHBCyz]',&.....%$-!)/rstBHGvEDDHCBCCBtsF~$..+%-~FKtvEEEEEEEEEEEEEGyzF);-%..+>{zyADEEvGxzr/^^sAEDGHCAy/{;,@..+=)^yBGDEEEELDHxtAGDEEEEEvvAz'@.+-(rxBGDEEDCCAszzsyBCHvEEvHtz;@.@-)^JDEEEEEEHAxyBDLEEEEDwr:;=..+,FsAGvEEvvCAszrrstBHvEEDDAx/;@...%,;)/sACCGLEEEEEEEEDGHCtzF!$........#,;)/ztDEEvGKF~;=&................%,;~FItEEEvyr]~-$@..............@$3~:KwEEEvAs/{;-*@........+%#,-;;'))~)))~~'~~~!;;;-%+..................................", +".+*!]zyCGLLLvGGHLvDLHCCCCAyzF~$+.+*-)]rsxBCCCCGGDvGvCvLLLvHAs],+.+%-']rtACHDLvGCCCvvvGCCBAzF);*....+%-!)F/zyBCHvLGLGHCCCBCBs/)$+..#-~]IyvLEEEuEELLLvLvAxrF);$@...>(ryCHDLDHysrr^rKtHvHCCBxr]!-#..@,)/sBHLELLLDHAByAHGLLLLLvHAr;@.+-)^yBGLLLLBCByssstCCGLLLvHAz;@.@=)^sGvvLLLvHAABCvLLLDvGtr{;$+.@-FstHLLLDHCAyzzzyACBLLLLGBsF;&...%-')/sBBCGGLLLLLLLLLvBBAsF;*+......+#-!{/syGLLEAsF~;,%................%,;)FKyLLELyr]~-=&..............@$;~FztLEuLts/]~-=#+.......%=-;!~)]F///FFF]{]]]])~;;,&..................................", +"..&>;{}IJwwwwwwJJJJJPKJKJKI[|;*...%>;~2IKKPsPJPJJJJwwwwwwOP[]~$..+@*=;{F^QPwwwwwJJJJJPsKK^F~-=&....+&>-~{F[IKJwJJJJJKKKzKzI/)-%...@*-;([8JOwOOOOJ6PP85Q|~!-=%....$;{2IJwwwP[2{((1:QPJJKKr^{!-*@..+*;)}QPwwNwOJPI[/[IPOwwNNwK[1-...*;)}IJwNNwKIr/Q^^QIKJwNwwP[{=+.+*-_}<6JOwwwPI[QKOwwOJ65[(;,#..+$!:[PwwwwJKI/F:F[IKJwwwwPQF~=....@*-;(FIKKJJJJJJwwwwwJKQ/]'-&.......+&$-!|[5OwNw6}~->*@................&*=-~2-_e5wNwwPQ:!->%@.......+&*=-!~)|}[[}}:]_))_]]1)!-$@..................................", +"..&$-!|28OwwwOO6<[[QQQIPOO8<|;*...#>c([8Ow65QQQ[[IP6OYRwO6<1_,#...&#>-!(12>#&+...&,_|[8RRM5[1(_a_(:[QQKIQ2(;,*@..+%=;(15OSwO8...%>c~:2Q56YO6[}[3c|%..................................", +"..+#,cdfhmppppig7fbee7hpqWqigc>...%-abiqWWoi7fffef4mmpnmni4b0,#...@%*,;abfijWUWWqqmg44774ea3=#......&$3ae7hmnqZpikhhhhlhhk7ba,%...@#*,;df7kkhlhh4eebbba9,*$&@....#>cbfpqZqmg:bdaabb77hhlkfb;,*@...#,9abijqZomk7feef47inqWqjgbc*..+*,cbfpjWWji47hlmhhghpqWqpgfc$...%=9adefhponig4gijopi7fbac=*@..+#>cafgmmmjppgfbeginjXmihfdc,&.....%=-a17hhhhhhilmmoqqigbba-=&.......@**3ae7moWWqiec9*&+................@%*30biqqUjgd9-*&...............+%=30biqWWomhea->%@.......&*>9~bf7gpjopihg77f77kh7b0,&..................................", +"..+#,cdfhVVXXVVkeeeee4hnWUWj7b,@.+*9dgpqUWqm441eef4hVVVXVV7ba3%...@#$>9ab4ioWWWqopi4ffeeeda9>&+....+&=3deklloZWonpmmVlVlVh7:c3&...@#$,0dekhlVVVkkeeb_a09,>#&+....&30b7mXoomk4bddbb4klVVVVk1a9*@..+@=;abhXoonmVlk4e4kVmnoZophb0%..+%90d7mXZonVlmmoonmllpoZZnhfd$+..&*,cabeglnoommmmoXXh7bdc3>#@...@=90:7klXnZom744ioZZmVh7ba9=%....+#=9ae7klllVmnjoooqqi7edc3*@.......&$>9dellZWWqpfac>$@................@*>30fiqUWqgb03>&+..............@#>9ceiqWWZXl4dc=*&......+&=3afhmmmnZoononpmmlVVVl4d9#..................................", +"..+$;~FzACCCCCBxszrzzsADEEEvy^!@.@-(^svuEEDHsszzzsxACCCCCBs^{;*...@#=;'{/sADLvvHHAszr^//])~;-%....+%=-~FrxBHGvEEDvDvHCCCAts^{;*...@*,;)FzxBCCBCtxz//(~';-,$&@....#;)/sBGBCCtsz^/^rsxACCCAAz]~-%..+#-'{/sACBGCBBxssstCCHHHCtz2~*..@>!)/zAHGHHHBvDuEEGHBHHHGHyr],...+$-!)FrzxHHvDvDGGHBsz/{~;-=@...&$!{^sxtHGvDHtyttDDDHAts/(!,&....&=-!{/zxytBCHEEuDDHHxz/]~;>&.......@=-~FsAHGLEvAJF)3=@................&>;~:QAEEEvyI]'-=@.............+#=;~{rtDEEDGts/~->*+.....%-;{^xHuvvGGHGvDuEuDvHCAAs/)*..................................", +"..@$;)FztCBCBCCBszszsyAGLELGx^;&.+-{/JAvELvHxsszzsyCBCCCCBs/);*+..+#=-!)/ztvDDCAxsz^F]{))!;-$%....+*=;'FrsACvLLEuLLvCABAAyz^{!*...&*=-'FryABCCBAsrF{)!;-,=*%+....%-(/ztCCBBysr]FF^zyACCCCxzF)-%...&$;~FryBCCCCBAssyABBCBAysr];*..+#;)FzyBBCHCHGEEELGCCCBCCtsr)=+..+*-;)]/zstCGLLEvHAxs^]);-$%+...+#>~FrzyAGLLvCACHGGHByzr]~-#+....&$-;)FrzsyABHLELLGCtsrF]~;$@.......@#-!{ryAvLEDHzF~;=&................%>;)FKALEuDxr]';*@..............%=;)FKALLLGAyz]'-$&+.....%-']IyGLLLvBCvLLEEELvHBCBs/'$..................................", +"..+&=-):^KKJPJJIQ/}[/QKOwww82(-+..#;)25wwwOPQ/[[/QIJJJJKKr[);$&....+%$,-!1QJwJK^}]_~;-->>**#@+.....&#>-'{/^KJwNNNNwwPK^^//F(;-%...+&*>-~{F/rIzI/})!;->==$%&@+....@>;)2rKKKI/F(_')(]F^QKKz[F';=@...+&=-'(]/^IKKrQFF//IzKr^[F);=&..+&=-~]^KKJJKKJYSNNOPKKJsKIF(;%....@*=-;'{2QKJwNNwPI}F~;-->%+.....+@*-')|}I6OJOOJOJJ5[F(!;$&+.....+&*,;~{]FF[Q8wNNwJKQF1~;-*%.........+%=-~:QOwww6}_->#@................+#=-_:@...............@*>;~:5wNwOQ}(;-$&.......@=-!|&.....@**-;d46Y8<}1(';>=*$*$%@.......@#*,;(|}[KOwwSwOOP>$*$%&@....+%-'([QIIQ2|(~!!(|}}QQIQ}|~,*+....@%=-!(|}[QI<}}:1[-;_2Q88RRRY8<}(;->*&+.......@$*-;_(|}4<6YSSO5[}|_'->>&+.........@*>-_e6RSO5:;-=#@................+&*-;b*$##&@.......@#*,;abefgimVmXmpnjmih7ebc,&...+@%*-;dbe4khk4bd0;-,=>***%@....+%>cb7hhh77ebddadbe4khlh7ba3#.....+%*,;abb7ipmg4ffhppifbdc9=&....%*,;afhhllllpqWUUqqjpmmh7ea,%....@%$,;0de7ipWWWjmkfba;,>$&+.......+%$,;af7ijUTUqigea9,*%@........@**-cdbfgijjjjpmh7ebbdc9>*+.........@#=3afjqUqiec3**@................+%*,0bgqUWjgdc=$@................#*-0biqWqjfd3=#&+.......+&*,cbgggheeffgigihi77b1dc-#...................................", +"..+#=9aehVVXoWZZnpnmXnXooXpgb;%+..&=3afinnnXVlVVlmnZZoXXVh4b0,%....+#>,;afhoZoikeed!c3>**%%+.......&*,9abe4kkklVVVXXZZZnVlk4d9#....@#*3ab4[hlVlkeb(a~c09c3>=*&...+*9_ekVVVlk7ee1b:4khVXVVk4dc=@....+@*=3adfhnZomhkhmoom7ba;=$%....&*,017lVXmXnnZWUUWWZoXXVhea3&...@#$,30_e4hVoWWWZmlk4dac,=#@.......@#=99bekmqUTUqmkba9=$&........+&$,9'b:7ioZonXVlh444ee{ac=#........+&*>3afjWUqpfac,*%@...............+*,30fiqUUqgbc3*&...............+#,9aeiWUWogb03=&.........&#=90bbfbbbbbffefff4ebdac,%+..................................", +"..+$;)/zACCHHHGvvDvvGHCHtysQ{!*...@>c{^sxtGCHCCCCCHGGCCBCByr]'*...+#,;_]^stDLDAAssr/{);-,*$@.......#-')/zxtBCACBCHCGvLGvHHBy^{=....&=-!{^sxACHHAyszzrrr^F])!-$+..@='FztCCCCCAyssssytBGGGHAsr]-#.....&*-;{FrxHvvHHBHGDDtK/{'-$%....*-!{^xACBGvvLLLLLEELGHBCtzF;%...%>;'{/rxtCGLEELDGHByz/]);-*+......%=-'(/stCGEEEDys^{;-#@........@#-'{/zstCDLvHHCCCAxtyyzr{;*........@*,;)2JGEEEGK/{;,*@..............+%=;~]KtDEEvyQ);-%...............%-;_[VwEEEvJr]~-*+........@*,;_1///}//}/}//^//^F]);-%...................................", +"..&*-)/sABCCCCCGvLvvGCAtsz/F)-*+..@*,!]/zsyACCBBCBCCBCCCCAsr]!$+..&=-~]/zyALLGCBtxsz/]~;-,*&.......#-!]rxBBCCCCCCCCCBHGGGLGtr(>+...+%-;(^zyAHGGCBtyyAAyxzr/]!>%..+>'FzyCCCBCBByyxABCCGvLGHyrF~#.....+#=-!{[zABGvvLvGHHAzF);-$@...+%,;{/sABCBGLLvCHGLELLCCAAz{;@..+*-!{/zsACGGGHHHHGGCAAsr]~-$%.....@#--']rsACHGvGtsrF);$#+........&=-~FzyACvGLvCCCCACBBBAtz/~=+.......&*,;)/KALvuts/)'-=%...............%=-;]KyvvLvy/);-*+.............+%>!{/sALELGxrF~-*.........+%$-!~)){))){)]){]]]))~',*&...................................", +"..+#=-~FIKJJJKKKP6PPIQ/:{)!;-#&....@#>c'){F2^QrQIrKKKJJJzI}{!-&...&*=-)|};){}IJwwJKIrQrKrI/[]_-=@..+*-!{/KzKzKzrQ^/rIKJwwwwJQ:)-&......+%*=-~:QIPwwwNwKI2(!-=%+.....+*=;~]/IKJwwwPIQIJwNOPI^/{;*+..+&=-']/QKJwwJQQIPJwwKI/2);,*@.....+#=--~{[IIKPKKQ}(;-$&+.........@*=;'FQKKwwwwKKrKrrzzKzr[{;*.........&*-;_}8Mww6}_;->*@...............@#$-;1>>>=->-,--,--==*#+....................................", +"..+&,-~|[QIQQQ[[}21:|(_~9--**%......&#$>-;!((|11:}2QQIPPI[}_;>&...+$>;~:2[5OYO6Q[[22:(!;>$#&+......%*-;|[QIIPPIIQQQQQI6YNNY<:;*+....@%$-;(|[5OYP$&.....&*>-;_1}[III-~|[#@..............@&*-9_:5OSR8ea-=#@.............@@&%%&&&@&%*#$$#%%@@+.....................................", +"..+%,;ab4hhlhh7ffbbda0c99>$#&@......@%*$=3390addbee44ilmlke0c,%...@*,0bf7kpqWqmhk774ebac3=#&+.....+#*,0|45lllmlhhkhhhipqUUjibc*.....@&*,3adfiqoplkhkhhkk74e03*@...&$,0b4hhhVlih7hhhlmoWWqpgba-@.......+%*,9af4ijWZopl7b0->*&.......@#*30bf7ijqjgffgiqqphfeb;,*+...%=;dfghioqqigf7ioqomhh7bc,*&....&%=3adbe7hilllh4bc-**@..........@#=-ae7hmjqWomhhhh5hllkked9#.........@*$,0fiqWoiedc-*#&+..............@@#>3abkhh7bc3*%@.............+&#=3cdfiqWqpf0-*#@.................+..+.+@@&&&&+.+.......................................", +"..+#,a14lVXlllh7e:bdac9,=**#&........+@#$$>-cadb:e4khVVVVl7ba3#...%,9d7kmmoZWqoVlhlmh7ed03=#@.....@&$-c:kVVVVXlXmnmnnnnjqqjgb9%.....+%*=3cbfinonmnmXXVVlk71a9=@...@*,9de4kVmXnmmmlmVXZWWWoke03#........@*>9ae7lnoZZnh41c,*#@.......&%>3a1f4inomh7egmoomk4ed03*@..+*3afkXmooZomk74loWZonmi7bc,%...+#>cdeghimpnXonXhea9>#&..........&*>9deklXnWZqnnmXmnXmVVl4d9*.........@#>3a7pWWWphbac>$#@..............+%$=3aekllkb0,>#@.............@#*>9db7mZWWjgd9>*@........................++++...........................................", +"..#-)/zxAHHHCCCtssr^]);-,=#&@.........+&*=-;!{FrzsABCHHHHBAz/)=..+*;(/yvDDuLEvGGCHGGvAs/]~-=%.....&#-;)^xCCCCCHvvDvvDvvvGGxK2)$......%=-;~]/stGGDLGvHCCtxz^]!-@..+%>;~{FrstCHvLDDGGHHvLELDts/'*........&#-!{/zxHHHHAszF);-#+.......%$-'{/zsxAHAyzzzyHBAssr/);$@..+>)/sGDDvvvHAAxxHvvEvvvHy/(;*...&,~]KtDvDEEEDvDDAKF';=*+.........%>-~FstCCGLEvvLDvDDvHCCAs/!$.........@$-!{KAvDDGyz^{!-=%+.............+%=-;{rxtys/);>*&............+#=-!)/zxGDLLAK:)3=&.......................................................................", +".+*;)^sAGvLLLvGCBssr]~--*&+............+&*>-!{/zxxCGGvLLLvBtr(>+..*;)^JGGvDvLGHCCAHvGxz/]~-$&+....+#=;)/sAACBCBGGDLvLDGAxsz^];#......+#,-!~FzstGvGvGCBtyzr/);*+...+$-;'{FrstCCvGLvHCBGvLGGAzF!#.........#>-;{/zxBBAyz/]~-$%+.......+%--)]^rsttyzr^rsxAxsr/])-$+..@='1zwGLDHCAAssstGvLLDDvy^);*+..#-'/swvLvuuuEuvutz]~->&+.........&=-']ztACHvLLLvLLGLGHBAxz];*+........@*-;)[sAHBAAyrF~;,#@..............@*>;~]rzz/{!-=#+............+#-;'_rsABAGAyr{;-#+.......................................................................", +"..%-;|[PONNwwwwJK[}]~;$#@................@#$,;)F[IPwwNwNNwKQ]'*...@=-~1<56P6PIIQQQ<<<2(!--*&.......&#=-')]F/[[[Q565655<[1();-$@.......@%*=,-_(}Q<85IQ2F{)~!-=&.....@#$=-;)1/[I5P6I$&+.........+%*-;!){{]{)!'!({]1))~;-*&....%,!|[55KIQ[/]1:}<55655[:0-*&...@$-~2I8JOOOOOO8P<1'-=#+...........%*,;)]}^-;|2Q[[}1(!-=*+........................................................................", +"..&$;_}[POSSSYO5Q}:~;>*&+.................&*=3~(}[IOwSNSY852(;*+..+%*-c_(1|||1((((|(a'9->*&.........@%*,-;;~_(((|111|1(_;--,*@.........@&#$=--ca||1_(!;;-->=&+......+&*=>-;_((|111((((|||_;-,#+..........&%$==-;!'!c->=#+............+#**,--;---->--;---,==#&+....&$>9'_|1((~~';;0_1111(_9-*#+....&=9a14[[2<<[<[}|';=*@............+%*>-;;_(111|||1111(_';->*&...........+%#-9!_((|(~;-**%+................@%#>$>>=$*&@...............@%=>;'||1(_~;-$*@.........................................................................", +"..&*3abfgimipi5g4b1a9=#@..................@#=3cdbfgiipipii4ba3%....&%$,,3c00000ccc;c9,>*%&+.........+%#*>,-39ccc;cc0;cc99>**%+..........@&#$>>,39;0cc,,>$*##@.........&#$=>39cc0c0c00;c;993>*&...........+@%$$=,-3,>=$#&..............+%#$*===*=$>>*,,=>$##&+.....@%*>,99cccc3-,,399ccc39=>*&.....&$,0abfbfffffbbd;3*%+.............+%*$>,99c0000c0c0cc9,,=*&+............&%$33c00a0c=>$&@..................@@#%%%%%@.................+&**,9'ac0c9,>#%+.........................................................................", +"..&=30d:47hhhkk4ebd03$%@..................@#$,0ab|47khhhh7fba3#....@%**,,-99c9c939,,,=*#&+...........@%***==,,,3>--3>-,,,*$#@............@%%$*=,,,,,,,****&+...........+&#$,,3-399933,33->=**@............+&#$$>$>==**%+...............@@&##$**%*##*#%*#%%@@......@%#*=,3,,,,,>>=>,,,9,,>$*#&....+&*39dbeeeeeeeeba9>*&+..............@%*$=,3,-3-399c99,,=*$#&............+@#*$=>--3-,>*%&+...................@@@&&&+..................+@#$=,3-93,,=*%+..........................................................................", +"..#,;)]^zssyyxsz^F])!-*#+.................@#=-~{F^zssysxssrF(;*....+&#$>3,9;;;;-,---->=#+............@%%#*>>--,333-3,---=*#&+............+&#*=>,>,3---=*##&+...........+&*=>--,--;;3;,-,-=$%#+............+@#**===$$#&&+.................+@&%%%%&&&@#%##%&++......@&##*>------>==-----3-,=##@.....%>;_F^rIrrKr^^/{~3$&...............@#%*$------->-;;---$###@............+&**=>------,$%+.....................+.++.....................@#=>--->--=#%&+..........................................................................", +"..@*,!)]/^zzrz^/{{~;>*&...................+&*>;~){F^zrzz^^/{'-&.....++@#*$$*#*#$$$**%%@+...............+++&***=$$==$$*#%#&@+...............+&%%%$$*$##@+................++&%%***$****$**&@@+...............+@&%%#%&@.+..........................++.+..++...........+.+&&%*%*%%##%%%#%%*%##&+......+%>;)({{]]]{{()~->#+.................++@&#**$#$=$****%%+................+&&&%*#$***%+.................................................+&**#$$**&@+............................................................................", +"...+@#*=---;;;-->$$%@++......................+&#=>--;--;---=$%+............+.+.+.+.+........................+.+..+.................................+.........................++...+...++.................................................................................+.++..+..++..++............+@#*$=$=$>=**#&+.......................++.+..+..+.+........................+.++........................................................+..+.+...............................................................................", +".....+++++++.+@+.................................+..+@@+@+++....................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"...+@&&&&&&&&&%&&&&&&&@+........................................................+...........................................................................................................................................................+&*%%%%*%%%@+.......................................................................................................................................................................................................................................................................", +"..+&*%#**#*$$>*=*$=>=**&+....................................................&%#%###%&+........................@&&&&%&@+................................................@&%%%@+.....@&%%@+.................+&#####&@+....................@&#$>>---->-,=*#@+.................................................+@&&&&&&@+........................+@@@@+.......................................................................................................................+@%&%%&@+............................................", +"...+&&&&&&%#*-,>=>,--==%@..................................................++@%#***%#%@......................+@#%%#%%%@@+.............................................+@#%%%#%%@@&@@&&%##&&@+..............@%**#%##&@+..................+&#*$>==,=,=,>=*$&&@..............................................+@@&%%##%%&@+.....................@@+@&%%&+.....................................................................................................................+@@%&%##&@++..........................................", +"...@&&&&%#*=3;;;-;!;c;-,*@.................................................+&#**>>=>**&@...................++&#*$$$$$*#%&@...........................................+&#*$$$$$*##%###$#$*$*%@.............@%#$$=>>*#%+.................+@**>,----;----->=$*&@............................................+&&%####$$#%%%&....................@%%%#%%%&+...................................................................................................................@&%%##$$*$#%@@+...................+@&@@@@@+............", +"..+&&&%#$,c0adbddaddaaa0;,#+..............................................+&%$>,3c93,>#&+................+@%**$=,,,->>*$%@+........................................+&&#$,-3,,,,=$*$>=>,,,=>$#+...........+&*=3-c33>*$@+...............@%#>=39aad~dddaaa;9,>$@................+++++++++@+...............+&%*$$>>,=,=>>**#@+................+@%$**===$$*&+...............................................................................................................++&#$*>==,====*#%@+...............@&#*$#$#**#&@+.........", +"..@%%*>30_b|447744eb:b:ebd9$..............................................@%>,ca1e1bac,$%+..............+%*=3ccaaadada03=$&+.......................................@%$,9!adddaa0ccccaaadaac9=#...........&$=3~be:bac=*@..............&%$>3'be7giiiiig74eba3=#+............+&%%#####*#*%%@+............+%$=>39;aadaaac;9,*#&@.............+#*-900a00c3=**&............................................................................................................+@%*$-90caaaaa00c3=*#&.............&#**>=3=3,,==*#%@.......", +".+#=>-)FrzyxBCCHABAxxxxxsz/~%...........................................+#=-!)FzyBByrF);>*@...........+&#,;'{F^rzzzzzr/{!;-*@..........@&%%%%%@+....+%#%%%@+......@#-;)Frzszszz/F]F/rzzzzr/])>@........+&#-'{rxBAxr]~-=@...........+&=-;']/sBGGDvNvGGABAsr);>&..........+&#=,,-,,,,-----$$%@........+%$>-!)]/^rrzzrr^/]{!;-=%+..........&*-;)]//rr//])!-=%+.........................................................................................................@#>-;')F/rrzzzrr/F{~;->%+.........@%>-;!)){)){))!;-=$&......", +"..%==;)rzyAGGvLGLGvGvGGCtyz{>...........................................@#-;)FrsACCtzF)!->#&+.........@*>-')FrzsxAtxxsrF)'-=#.........&#=$>>>=*#%&%##$=>>==#&.....%>-;{rsBAtAxszr^/zsxytAyz/{;#.........%=;'FsABCBzF~;>#............#,-!)/sAHGGDDvDDvHCBAr]!-#.........@&*>-;;;;;--;;'!!;-=#+......+&*>;')]/rssxxyxszr^F]);-$@.........&*,;)]/rzzszrr/{!-$&+.......................................................................................................@*=-!'{FrzssyyyxzzrF])!;,%........+#>-!){F//^^/^F{~;->*&.....", +"..%$=-~][IJwwwwNNwNwNwwwK[F~>@.........................................+&#=-;_2IJJJKQ|~;-=*&@.........@%*-;~2QIPIPJKPI[|!;-*%........&%#**=*$$**#**$***=$=$*#+....&*,-~}IJJJJPK[}]2[IPPPJKI}_-%.........%*,;)[KJJK})->*&...........+#=;!~:[PJJJJJJJJJJKKrF~-=%.......++&%*=,-;!;;->-;~~'-,*%+.....+&&$,;;~1}QIKPKPKPIQ}:)';,$@.........@#*-'(F2F/[/QQ[(!>*&+.......................................................................................................@*=-;~):[IPIKPPKI<[1(~;-=%.........%=,;)12Q<<3~(}5OwY6[1_;-,*#%@.......@#$>-!176OYwOYYOO5}(!-=#+......@@#>,-,--->=>*>=>>,---,=%@....@*,;(78OYRRM6<}:}<8YSMRR8[(;*.........@$>3([OOO84~-=#@...........+*,;~(:<6O85<<<<-;~()!--;_{:(!,$%+.....@&#*,;(12<8OYOMwYYO8<}1(~3=%........+&*>-'|}[[}2Q565}'-$#@......................................................................................................@&$>;__1[58OYOYOMOO6[:(_;-#+........&$-~:[<8OOOYO5}('->*@+....", +"..&*,;abgpqWUUWUWUUTUTUWoifbc*.......................................+&#$=-cdbgmqUWoifba;3>*&+.......@#=,;afiqWWWUWWWqjgba9=*+.....+%**3900cc;c3939399;c0cc3*@....&*3cbgqTTTTTqigffiqTTTTUqgb3#........+&*,;bgqWqjgd9=%@..........+%=-;dbgpqojlhhhhhkhll7ec9*%........@&*=-;adbba!c;deee0-*%@.....@#$=;abf7iqWWWWUWWWqp74bdc>%.......+@%*-cde47777ipqjga9=#@...........................................+++....+++++..............................................+&#*30defkjqWWUWWWWqji7ebc-%........+#*9afimqWWWWWqgba;>*&.....", +".+&-,0d4moWUTTTUUUUTTUTUqnhfd,@...............+@&%&@+................@#=,c~be7ijUUUomh4|d0c>$&.......@*>3abfpZWUUWUUUWjhed;3*&.....@#*-;d(bb:bbd(ddd(dbb1ddc3#....&$9cepqUTTTTUjhgijWTTTTTqib9%........+#=3abiqUUoib03>&..........%#>cdbehpWWnmlllllVVVVkea-=&........@%$,c0dbee(0cab4ked3$#@....+&*3ca:7klnZWUUUWUUWWnVkked9*.......+%$,9_ekllQllVoZqib9,*%................+@+++++&&@@@@@@+........+@&%%&#%###**###%&&&+........................................@*=9cd4khmnWWUUUUUUWZXhk410,@.......&#,c|7lnZWUWUWqmed03=&.....", +".+%-!{^sGDEEEEEEEEEEEEEEEvts^'&.............&&**>-=$$#&@...........+&*-!{FrzyBHDEEEEHAAsz/]'-%......@#-'{FzyGLELLLLEELGsrF);-%.....&>-~]rsssxysxsxsysyyxssrF~;&...%-'1IwDEEEEEuvAttvuEEEEEEAI)=........&$-)FKAEEEDyr]~-$+........@=-!{^zxAvEEDCBCCCCBCCCAz]~;*........&*-;)]/rzr/{)]^sxzF~-=%....&>-~]rsBHHGvLLLEELLLLDHHAtrF-&.....&*=-~{^stACACBHGLvyI{'-=@............&#*$>=$*$=>,,,>==**&.....+@#=,---;;;;;;;;;;;--,>*%&+....................................%,;)FsyCCGLLEELELELLvGHAAs/($.......#-;)^yAGLLLLLLGtz}{!-=@....", +"..%,;{/sHLEEEEEEEEEEEEEEELBsF;&............@#>=---;--=*%@..........@#-;)FrsACGLLEEELELGtsrF);*+.....@>;)FrKxCLLGCCCAHAys/{';$&....+#-')/stBBHHGGGGGGGHCCBAs/);*...%>;)^sAGGvvLvHByAHLEEvvGts/)*.......+&=-']KtGLuLxr]!-$&........@$;~FrsyCvLLLCCCABABAAAxzF~-*+.......@%-;))]F^/])))Frr/]!-=&....#,!{/stGGGLCvGLLLLLGHGvGGts/~#.....&*-;){/sACCCCCBvLDAr]~-=%..........+%*=--;-;-;--;;;;---=#&...+%==-;;'!!))))))))~~~!'--=*@...................................+%-!{/sGvGLLLLEELLLGGGGLGHtz{,+.....+#>-)/zyBBCBHCHAsr]);,*&....", +"..+#>;_48wNNNuNuNuNuTuNSNw52)=+............@&$>=,---,$$%@+.........@%$-!{/[IJwwNwwNwNwwI[|);,%.....+@*>;)12QPwwPQ[2}}}|(!;=*%+....+&$-!{/rKKwwwwNwwwwwJJzI^]!-&...+#=-~|}QIPOwOPQ[QPOwOKIQ}|!-@........@#>-':-;)]/IKzKKKJJwO<1~-=#@.........+&%**,,----;----;--,,>*@...@%$=,-;!!!~~)_)~~)~!;;;--*$@.................+.++..............+%=-~:IJwwwNuwNuNwwJJJwwwOK})=.......+%*-'(]}22[}}22(~;,>$&+....", +"...&>c_}5RwNNNNNNTuNTNNNSO<:~=+............@#*=---;;-,*%&+........+&%=-!(}[*@.........+%-9_e[,,=**=---,=*%@+....@=-!(}%@.........@%$>--;'!!!!!'!_~~;;--*@...+*=-;_(1b:1::b:1:111{|1_c-*&..............++@@%&&%&&@@@+........%=;_|<6OwwwwNwOwwOJJJOOw8<:~>+.......+#=-;___((___~;-=$#@+.....", +"...@=9afpqWUUTTUTTTTTTTTUjibc$.............&$>3caa_ac9>#&@........+%$=;de4hhlmmiigiimmiged0;=@.....@%$,;dbf7imi7fbaa0;93,=#&+.....@#*3ab7hllVnmmmmmmnmmllk7ec3&....@%$3;dbfgpqqmh4gmjqpgfbdc,*.........@%$,9bgqWWjgd;=$&.........+#-0fipjojppjjojpih7e:bba3=%@...........@%**$$$$***>>>$#&@.....+&=9abgkmmXoqqppppmlmllmmmkge0*.....@%>c0bb4hhk77khpqjib09>$&........+%*=3cdbbeeebbdbeefbba0-%...&*-cbgiijijijpijpijpiiigbc,#........+@@&%&%###**#**#%%#%+.......#,0bfhimmnqWqopmmmooommmikfb>.........&*=-,cc;cc993,>**&++.....", +"...&=9d4oZUUTUTUTTTTTTTUWZif0#............&%=-ab1eee1a3$#@........@*=9abklVlVVlh744khVl74:d0,#.....+*=3a|4kklVlh4:ba!99,>$#@......@*>9aekVlVXVXVllVVVXlXVVhed3#....+%*>ca|ehnZZpllVXZZnhebac,#+........@#*-0biqUWqgb03=%.........&,0bgpZWZoXXoWWWZnllhk4|dc,$@............+@%%#%%%%##**#&+.......%,017lVmXXoWWZnVVVVlXXVVVlh4d=....+%>-~be4klVVlhhVoZqi7dc3*&.......&#*3;a:4khkhkk4ekklll7eb0*..+%,0biqqWWWWWWWWWWWWWUWWjgbc$@......@%**===,,,,333,,,,>==*%+....+#;_eklVVVnZWWoXVmnoWonmVVlhe9&........+%*=,=,,,>-,,***&+.......", +"...#-_/JGEEEEuEEEuEuEEEEELw/!#...........@#,;)Fzzsssr/'-*#+......+%=;)FztBCHCBBtsssxACBxsr/);*.....&>;)/zxACCCCBxsr/{);;-,*&......%>;)/sACCCCCCCCCCBCCCBCBBs^)=....+%*-!{/zxvDDGHCHvDEvxz^]~;$@........&=3c(kAEEEGxQ);-=&.......+*!FztDLELDHGvEEEELHCAtyz/{~-%..............+@@@&%&&%&%&@........*!]zxCBCCHDvEGHHCCCCCCCCCCCxr;....@$-~FrsxCBCCACBHGDDAs^{~-$@....+%>;;)]rxACCBCBAyyABCCCAxr{-..+$!{IxEEEEEEuEEEEEEEELEEDyKF!#....+&*,-;!))))(){]]{)))~';->*+...&,)/sABCCCGLELGHBCHDEEGHCCBAs{#.........&#*$>,----==**&@........", +"..+%-_/sHLLDEELEEuuEuEEEEvy^;%...........+#,;)/zssxszF);>*@.......&=;)FzACCCACByszzytBtsz^]);*+...@#-;)/sACCCCCAAssr/{~;-=*@.....+%-;)/sACCBCCBCBABCCCCCCCAs^)$+...+&>-;)/zsHLvvCHCvGvHyz/]~-*+.......+&$-;{rJvLuvy/{;-$@........$'FKAGLLLvCHvLEELGCBCBAxr]);*+...................++.++.........+='/zACCCCCGLLvBCACCCCCCCBCAA^-+...@*-)FrstABCCCACCGLLts/]~;=@....%=-;~]/zyCCCCCCAAABCACAAyr{=+.+$;{kyHGvLDvLDDDLDLvLLLLDAzF)$....@#-;'~{]F//^^^/^/^//F])~;-#...&!{ryBCCCCHGLLGCCCGvvvCBCCCAs{$...........+@@%***%##%&@.........", +"...&=;_[6wNNNuSNuuSuSuNNNw<1;&............&*-;~]FFFF|)-,*&........+#=-):rKKKKI^/2F:F}//]_~;=*%....@#>-':QKJJJwJJKII[1);,$%@.......@*--~FIKzKKIrI^rQrIKKKKKr});%.....+%*,;!1[!1QOwNwwJPJJOOJJPKJKPI[_;,%...................................*;)F/KKzKJwwwOKQ/QrIKIIrIKr^|>+....&*-!(]/QzKzrKIKJOJ<2{';=&.....%=-;!):[IKJKIrIrrrKzKzK/F_;*...%=;_}<5P6P6J6666JOJwwNwO<})-#....&%=;')|}}-~1}[[2(!-=%@.........@>9_48SwYOPII<%+...+&*-;(1}Q5OP<[[[QQIIIII[}('-%...@#>3_12}[[[[[[[[[Q<6YwS6<1!,%...+&$>!(|2<66OOOOOY8Y6YY6<(3=%...+#;(}QQQQQ5OSYOPIIIIPIIQQQQ[}-+.................................", +"...&=9afpqWUUUTTTTTTTTUUWqgb0*............&*=3cadbbbda-=#&+.......+%=3ab7klhk4fe1bbdbbdac9>$#&....@#$9agpqWUUTUWWqqpgba-$#@.......@$>-a:7hlhh74feeee7khhlhhe09%......+%*,3abfhpqTTTqmh4bdc3=%@..........&%>90bffffdc,>#@.........@*30fiqWqjmkhkhhhhlpqWWqiea3#...................................%>0b7hllmmooqpif4e4khkkhkhk4b>....+&*,;a(efhlllmlVlmh7eba;=%+...+#,;de7gpjqoi44775ilhlh7ba;=%...@%$3cadbbbbbebfff7gpqWWjgba3#...+#$-abf7ijqqqqqqWqWqqqjif0,%....*9de4hkhkijWWqpmmlllkhhhkhkf9+.................................", +"..+%,0d7jWUUUUTTTTTTTTUUWqmfc*............@*30de47744(0,$%+.......+*=9_}kVVVll<7444444e:dc9,*&....@*,3afpoqqUUUWZZom4eac,*%+......@*,ca4llVlVVhkk7kkllVVVlh4d3%.......&=3cdeklnqTTTqXVk4bac>$&..........@$>9dekhk7eac=*&.........@*,cbgpnnnXmnmpmmmXnWUUqp7b9*+..................................%301klmnnnZZWZpmilimmVVllll7b=+...@%$3adb4hXnXnXonXnXh4:ba9$@...%=3dfimmoooomlllmXnnVllk:d9,&...@#*,30aadddddbbbe4hmZWWqmfd3%...@%>9aeehlXoZoZZoZoZoZojibc>%...+*0bQkhhlVXqUUonnXXVVVVllllkec&.................................", +"..&>;(^JvEEEEEEEuEEuEEEELDyI)*...........@#-~FzyBCCAxz]~->#+......&=;~FzACBCCCCBAytABAAsrF{!-*....&$;_2zyHHGDEEEGBCAsrF);-=%+....+%>;)/sACCCCCCABBCABCCCCCAs^)$......@$;)]^stBGLEEEEvCAsrF)!-%.........+#-;)^stCAAzF);,*@........&=9~:^zxAHCHvDDGGGHDEEEDts^{,+.................................+*;{/sAGDvDEEEELvLEGvvvHCCCAsF-+...&*,!{^zxBDLEDvLEDDvAsz^]!>@..+*;{^xGvDvvGCCCCBHDDDCAysrF~;*...@#$,3!~_))_{]]]F^zsGDEEvAK^)=...&$-!]/zsxAACCCBCBBCHBByK2~9$...+=)/sACCBHGDEEDHHCBCCHCCCBBtz~%.................................", +"..&-!{/svLEEEuuuuuEuEEELELy^'*...........&>;)/sACCCCBs/);,#@......@=-'FzyACCCCACBBBBACAyz/{'-#+...+=-~]rstCCGLLvCCBxzrF{!--*&.....#-')/zACCBCBCCCCACCCCBCCAs/)=+.....&=;)FrsACvLEEELCCtsrF);-%.........&$-')/sBCCtz/);-*+........@*-;~]^zsxtABHGGGGLLGGGGAzF)=+..................................*;{^sAGLDEELLEEuLEuLLvBCCAAs/-+...&*,;)/ztAvLLLELLELvHxzr]!>@...*-{QyDLLLGCBCCBCHLLGCAxzr])-*+...@#=>--;;;;!'))]F/sALLLDAz/)>+..+#>!{/rzssyyxAAAAAtxysz^]'-%+..+=~/zyBCCBHLLvGCBCCCHCBCCCByr'&.................................", +"..@*,;([6NNSuuuuNuNuSNuwNO<|-&..........+%*-;1[KJJJJPQ|!->&+.......&$,!{^rKzKzKzQ/rrIKr/]_!-=&.....&=-'(F[IKJwwJIIQ[]|';-==*#+....+*,;)2rKKKKzKzrKrKzKsKKKr})-%......@*=;!(F[KPwNNNwPK^/('-,*&.........@&*-;_/IKzI}~->*&.........+%=,-;~(]F22[QQIPwwwwJKI[(!-#...................................%$;)}QJwNwNwNNNNNNNNNwJKzK/F!*....+&*=-~(25wwNNNNwNwOK}:)!-%....&=;($$=>>-'_}$%@....+&=-!1}QKIPPI-9'(11:::1[[>;_1[6OwwwwwMO6[1_!;=%....&=9~[8ORY8PIQQQ<6YY8<[}1~;-*&......+.++@&&&%%#$==9_26OO52(;-%.....@*>--;'______(_((_~!!;-=#+....#=;_|[QKPOwSwP9agpqTTTTTTTTUTTTTUWjgb;%..........+&*-aepqWUTTqiec9=@........@#$,9d:f4hhmpjjjjmk7eda9>*&.....%*,;dbf7ioqqpgeebbba~0;3,*&+....&*,cb7khlmpojjjoojopmhk7b0,&......@$=-cdbf7ijUTUji7ebdc-=*@.........+&$-cdgmjjiea9=#@..........@*=;abbfeeeeef7gjqqpik4b03*@...................................+%=9abgmjnpnoXpnnooqomlhh7:a3%.....@&$=3cdfgiiiVimiihfda9,*&....&*-0fgmppjjpih7hippm5k74bc9=&............+@@&&%$=,0bfgggba9=&....+@%*=>--;99cc9;cc;cc9-,$#@.....&*-0de7hmXoWqplhhippnmmlkfb0*..................................", +"..&*,abgjUTTTTTUUUUTTTTUUqmfa$..........@*,0a7jZUTTTUjfa9>#+.......@&*>9ad:ekVXqUUUqoml4bc9,*&....+%=3db[klmoWZph44eee:b|bd03=+....&*9aeklVXoZZWZWWWUWZXVlkea,&......@*>cabekhpqUTTqmh[ebac>$@.........@#=3abioZWoib03=%..........&=caekkkk444e47loWZnhkf(c,>&...................................+%=90b7lXXXVXXXXmXnnXXXVV7e03&.....+%*,90abe44hhhhh7fbac9,*&....@=,9d44lXoZoXlhlVVXXVVVhedc=&...............+@%%$,9abfebd;,*+.....+&%#$*==>,-,-,,,3,,==$*@......&*=3dekVVnZZZnmlVXZZnXXVh4d9*..................................", +".+*-'FztvEEuEEELEuEEEEEELvtK]-+.........&,;{QsDEEEEuuuz});>@.......@%=-;)]/zyBHDEEEEDCxz/)!->%....@>;)FztCHHvEEvAAxyysxysyz^{;%...@*,']zxBBCGLLELLEELDGHCAyr{;%.....+#-;)FrsxBGDuuEEGAtsrF{;-%.........@*-']rtLEvvyr]~-=@........+$;{rsBCCCAxxsxxHvDDGByz/);,#...................................@#-;)FztBCCCCHCBHCGHCBCBBx^{-%.....+#*-;~)F^zzsssszr^:(';-*&....@$9~FrsAHGDvGBBBCCCBCCCBsr{!$................+&#=-'(F//F(!-*+......@#*>=,-----,-,>---,$#&+......#,;~FzxBCCDDvDHHBGvDvGCCAz/~>..................................", +"..*-']IyBLLELLLLELELLELLvHyzF-+.........#,!{[sHLEEEEEtzF~;,#........+@*,;)]^zyBGuEELHtsr]~;-$+....#-!]^sBvLLEEELvHGHGGHGvHAs/'$...@*,;{/sxtABCCCBCAAHHABtys^),%.....&#-!)/rxBGLEEEELLGBsz/]'-*.........&=-~]IyGLLvtr]~-=&........&>;]ryBAACBByxyAAvLvBtsrF);=&...................................+#=;']^sABCCCCCLvvCBCCByxzF),+.....+@*=-;!){F/F////F{)';-$%@....@$-'(/rsxBHCCCBCCACCCCCCyzF)=+.................@#>;')())!;=%.......++&&%####%*#%%#%**%%+........&$-!)/zxyBCCGGGvvvvGCBAysr{;#..................................", +"..&=-~1}IPwwNNNNNNNNuwwwP<}(;$..........@*=;_[6wNNSNN8}_;=*@.........+@#=,-!)1QPwNwO<2{);,*%@.....@=-!|-;~1}[IKKKKzI^QrKKKK/}_;*...................+&*=,-==#%@................+...+....+...........+@*=-'){F[QKPJwwwwsKI[F{)!-*+..................................", +".+%*,;(1}Q#@...........+&#=,;_1-''___~~';;;;;;'__';;=&......@%*,;_|[8MNNNNNNwY52(~;-*@..........@*-'b*****$*#%@+.........@%*=-;(:}[[QQQ[21[QIIQ[:(;*....................+@&%%%&&+.......................................+@*=>-!~|1[IJwNwOPQ}:_!;-=%+..................................", +"..&*>;_b:f7g5iiiiii30be47k774eef4hhkeba3#......................+@++............................................@#>,30defimqqqpi7e(a0->*&...................................", +"..@#=3abb}ef7474477777feba09=@..........@#>9afpqWUqUqifc3>%@.............@#$,cdgpqjifc3>*&........@*>cdfpqqWWWWqZoZoZZZZZopgb3%......@*>>-9-3-,,,>>>=-9;93-$*@......@#$>90bfmoqqUUWWqoi7dac>$@.........+%*,cbiqUWqgbc3*@..........&=cdeklVnZWWqZZZooifd;9>*@........................................+@#$>99abegmpopi4ed~33=#@..............++@&@&&@+................+&#=,9ade444eeb|e4774edc3%......................................................................+%*>330de4imjnmh4bda3,*#@...................................", +".+&=-;)/^rrzrQ^QrKrrKrr^])!;-@..........%=-!:rtGvvvDGyQ{!-=&.............@*,9!{rxtyP^|;3=&........&>3~:rxtGGHHGHGHHHHGHHHAtKF'*......+%$------->======-;--=##@......@#,-')FQstGGGvGGAAsK/{~-=@........+@=3c{IxDEEGx[);,%.........+*-~FrsyBHGGvHHHBAxK/{';>#@.........................................+%*,;!)]/rzsKzK/]_~;--#+...............+@@@++...................@%>;!){]/^///FF/^/^/F{~;#.......................................................................&*--;')F/zsszKr/{)~;-*#@...................................", +"..&$-;)_]]///FF///^^//F]);;-#+..........@*-;)^stCBCCtsr);-$&..............%$-;'F^zz/{';-%+........&*-!)/rssxsxxxyxxAttxxssr/(;#.......+@&*$$$*$####%&%%*#*%@........+#=-;~_FrssxsxsxssrF])!-#@.........@#,')QyGGLvs/);,#+.........#-!]/zsxtBBBAAyssrF{!;,#&...........................................+@#=;;!){]FFF]{~';->%+..........................................+%=-;;)){{)))))){)))!;$@........................................................................@#>--!~{]1FF]{)!;->%@.....................................", +"..+&$=--,;-;;;;;;;-!!;;,-=*%+............&*=-~][^QrQ^F_;,*@...............+@#>,;~~~!-,*&+..........+%=-;'(({{{{]]]]]]]1{()~'-*+............++.@+...+.+.+.............+%**,-;!(({1{1{({~;-,=*@...........&*,;([58P52_-=*@..........@#=-!)1]F////}/1(~;-=*&...............................................+@##=>-------=$$%@..............................................@%$*=>>-->>->,--=>=*&..........................................................................@%$*==>--,3--->$#&+......................................", +"...@%#%%%%#%#%%#**$***%%%&@+.............+%$=;_|:}12|~;>*&+.................+&*>>-->>#&+............+&#>,-;--3;;;;3;-;;;--->*&+........................................@%**=--;-3--;---=**%&+...........@#$,c(}[2:_;,*&............@#>-;!__(|(((_~';,=*@+..................................................&&##**=***#%@+................................................+@%%%#&&##%###%$#%&+............................................................................+@&#%#*$***%&&@+.......................................", +"......++++++..+++...+++..+................&*$,caa_da09,$&@...................@&*#**%%&&..............@%#*$=,,,,=,,,-,>=>=**%&...........................................@%##*$=,,,>===$$%@++.............&*>9adbbdc9>%@...........+@%#>,339;000cc3-=$%&......................................................++&@&@@&++........................................................+.+@&&@%@@@@+.................................................................................++@&&&&@@+.........................................", +"..........................................+&#=,,99399,=*&.....................@&%%%&@@...............+&%#***$=*>=*******#*&@+............................................+@&#****$***#*&@+...............@%=,00bdac=*%@............+@%*$>,,3>,,,,>**&@..........................................................+..................................................................++@+.........................................................................................+@+.............................................", +"..........................................++&%=>>-,=>>*%+......................+@@@+..................@@&##%#%%#%%%***#%#%&.................................................@%#%%####%%@.................+&#=-;~~';=*%+.............++%**$$>==>=**##%@..........................................................................................................................................................................................................................................................................", +"............................................+++++@@@+++..........................+......................+..+.++.++++++.+.......................................................++.+..+...................+++@&%##&&@+...................@@++@@+@++..............................................................................................................................................................................................................................................................................", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"................................................................................................................................................................................................................+...++...............................................................................................................................................................................+@&&&&@+.......................+@&&@+..........................+@%%%%%%&@+.................................................", +"............+@&&&&&@@+......................................................@&%&%%%%&@+...................+&&%&&&&@++....................................................................................+@&&%#####%*####%&@..................................................................................+@@@@+..............................................................................+%##==,,>$$%@+.................+%%$=,3>$*%@+....................&%$>3-3333=$##&@..............................................", +"..........+@#=>333,>*#@...................................................@#*=,3--33=*%@................+@*=>-3333==#@+.................................................................................+%*=,3,39;9c;9c9;33$%+.............................................................................+&%***$*#@+............................................................................#=,ccddddac3=#@................%>9cadddac3=#@+.................+*3caddb{b(a0c,=*&+............................................", +"........+%=,;~{]]]{';->#@...............................................@#=-;){]{F]]);->%+............+&=-;~{]FFF])!-$*@+.......................+&&&@+................................................+%$,;')]F/^^^^r^^//F{~-#...........................................................................@%$>-;'~!!;,*&+....................@%##**%%@+...........................................%>']/rsxysr/]'-$%+.............&,)Frzxxxz^F);$#@.......+@+.....+$~]/zxyxAxysrF]~;,##@@@@@+.....................................", +"........%=-;']F/r/F]~;-=#@.......................@@@+..................@#>-!)]Frr^//]);-=#@...........&=>;!{]//^//])!-=*%@..................+&%%##==*#@.............................................@#$>-;;)F^rrzzszzszzr//{;=+.........................................................................@&*,-!){{])!;-=@.................@%#*$=>,>,==##@........................................+%-)FrstBAAxrF);,*&+............&-]rzxBBBxzF]~-=*@..+@&%##$#&...@,{/zyBBCCBBxsr/]~-,*$#%%*#*&..................+@@&@+...........", +"......+&*=,-!)(]]{_);-,=*@...................+@&@&&&@+.................&*=,-!){]]F]_);-=$%@..........+&$=-;~{|F1]|)~;-$*@@..................+&%#$$$>*#&+...........................................++%$==-;!)]F]FF/FF/}FF]_'-%.........................................................................+&#*>-;!)))~;=*$@................+&#$$=>=,,-=>**%&.......................................+%=;)F/rrKr/F_'-=#@++....@@+....@$;{F/rKr^/|!;=*%&@+@%%%#***%+..+$;)]/rzzzzK^F]_~;,*#%%%#$$*%.................@@%&%&&@+.........", +"......@&#=-;!_(|||1(_;->*&+..................@%#*$$*#&+...............+%#>-!_(||::11('-,**&+.........@%*>;~((|::||((!-,$#@+................+&%***===>*#%+..........................................@%*=,;!~((:}}4}44[}4}}:1!-#+........................................................................@#*$,-!_|||('->%&................@%*=,--;;;;--,$*&&.......................................%=;_|2QI<<}|_;=$%&@.@+@&@%&@...+$!(}[QI<[:(!->$%%&%##$==>>=#@...*!(:[QQIIIQ[}|(~;,>*$$$*===#+...............+&##$#$#%&+........", +"......&*$-0dbeeefeeebd;,>*@.................+%*>333,>$&@..............+%*=cdbefeef4eeba;,=%@.........&*=9abeee4444ebbc9>*&+...............@&*>339cccc>*%@.........................................+%$>90abefgipjjjjijijipigb;#........................................................................+&#=-cabfgiigbc-*#+..............+&#$3;adbdbdaa;3>$#@......................................%=3ab7ipmphfd0,*#&@@&&&##$$*+...*cbfimpmi7ba;=$##%#*=,9cc09=@..+$0be47hhkhh77eedc->**=339cc-&..............+&**====$$%@........", +"....+&#>-0|7klhklkklk4d03=*@...............+#*3caadac,$#@.............@#$90ekkkkhklVk4edc3>%+.......+%*-c|khlkhkllhk2bac>*&..............+&=>;ad(bbedc3$&+........................................&*=3_e7hlmoqqqTTTUUUWWqqpgd,@.......................................................................&#>30degmqUqjgb9,*&+............@#*=3!be7kkkk4ebac,*%@.....................................#,0beinZZomke0;,$##%#*>>333,*..+>_bkmoZZXh4b0-==*=>3;adb4ebc#..+>dek-~{/stHDuEuw^{'-#@..........+&=-;~{^stBCBCCAAsr]);,$@...................................+*;{/stDEEDHtz/);-,---;!){FF]-@.+-FzxHLELvAs^{';;;!~{FrzsAyz],..@;FsxACCCACCCCCByrF{))F/rsxsr;@............+*-~]zssrF)-=%.......", +"...@*-!)F/sACBBAABBCCAz/]);-#@...........&=,;!]zxHAxr{;->*%+..........&=;~FztABABABCAAs^])-=&.......&$-']ryBBACBCHGByz/])-=&............&*-!)/zyAAtsz]~-=&..........+&%%%##%&@@...+@%%##&+.......@$;']rAGGvLvDvEEEEEEvvGGvtsF;%.......................................................................&=-;)FzyCDEEDtr]~-=&..........@*>-~{FzxBBABBABBts/{~;=#+...................................#;)FzyBHHGHyz/);;--;')]F/rr/;@.@-]/stGGGHAsr]~;;')]/zsyAyyz{>+.@-FrytAACCCCBBBAsz/F]/rzsAAxr;@............&=;)/sAAx/);=*@......", +"...@%>;!(][rKrr//^rKzQ/()~->&+..........+&*=-;(Q6wwP[~;,*#&@..........@#>-~{/^^/^rIzK^F)!->*@........&*,;)F^^rIKJwwKQ]_;-*%@............&#=-~]/rrr^F(!-$%+..........@&%#%#*%%%%&@@&&%&%#%&@......&*,;)}6wNNw6IPwSNSwOPKIIQ4:~=+.......................................................................+%*=-'(}%@..........@#=-;){/rKr/^^rrKr/]);-=&............+++.....+..............+&=-'([IKPKI[F~;>>=>,-~(]F[^F-@.+$;_:QKPKKQF_~;--;~)F//^//F{;$..@=;)]F^QKJwwJK^/F](_({F//r^/(=+............@#>;_F^^/(;,#%+......", +"...@%*-'(12QQQ221}[Q[[}1(~-*&+.........+@#$>-c(7OSM64(;->*#%&.........@#*,;_11222}[QQ[(~--*&........+@*,;_|2[2QQIOO84_!-=$#@...........+@*$-!|[IQ[1(~->*&+.........@&##*$=$$*####*###$$>$$%%+....+&>3_b5MNNM5<5YSNSO6I-!~|}QQ[2222QQQ}|~!-=%+.......++@@@@@&@@@&@&@@+...........@*-;_|[IQI[}(_;->>=,;_|:}}[1-@.+*,'1}QII[[}(!;--;_(:[QQQ}1_-%...#-;(|}[QPOO8<2::(((|:}2[[[2~>.............&*,-_|221~-=*&+......", +"...@#>9adefkhh7e1f7hh74bda3$@.........@%*=,90dfiqWWjgba;9>*##+........+%*,9_be:e447hkfba9,*&........+&*-9abe477hpoqpgbac=$%@............@*=30b4h<7edc3$%&..........&**>-33->==**$*>>,-33,,=#&....+%,cafiqTTqigijUTWqolh77bbc3%.................+++++...................................................+&*,9abijUUjgb0-$%@..........&*,;ab:7kh7fee44hh4bba;=*+......+&&%#$#%*&%%%##%&%@+.........&*,cde4hhk74bd03>>>3adee477f9@.+$,0b4khhh4ebd00cadbf#%+......", +"...+*>9abeklVVh74kkVll7eba9=&.......+%*=-9cdbbgpWUWqm7ed~c93*%........@#$-0be47kkkllkk1a;3*&+.......+#*-cd:4kkllXoonhedc9=%&............&*>3_e4khk:ac,*#@.........&*,c!ad(da093,=,3cddbddac3$&...+#-cbfjUTTTjmmqUTTWZnVhk41a-#+...............@&##%&@+..................................................&>,cafiqWUWieac,*@..........@*9'be4kllIk[kklVlk4:ba9*&....+%*>,3333,=,,-3,-,,=*%@........&>9ab4lVVVlk7ed'c;0d|47kh@......+&=-'{/zxBBBCCCAsrF~;-*@.......@$-;)/zsABCBHHvHtszF);=*+...........#>;~]^ssyz/{~;=#+........+=;{/zsxAxzr/])~)]zxAtxszr]!$+..+$!1QJwuuTEGHHvEEEEGHCAtyrF~,@.............@#>>---->$*%@..............................................+%-;{FzADEEEtKF);,*+........@$-~]rzyACCAtyxBACCByz^]~-#...&#-;){FF]]{){{]]]]{)';-=%@.....+*;)FzsCCCCAAyzz^//rzxtCCCAxz'&.+-~^zACCCCCyyzrrzstACCCCAsz/(>..@;FrsttBCHvGGHCACBAABCACBys^(=............%,;~]rzyyyrF)-=%......", +"...@$-;)]rsABCBBBBBCAAz/]);>#+....&*>-')/rzsxABvEEELHBAxyzr{);#.......#=-!]^stACCBBtxz/{~;,=%.......&$-;)/zxBACCBCBCBsr/);>*@...........&=-;{F^rr/]~;-*@.........&>'FryABBBAxr/F){/JGLHCBts/)-%..+$!]^svEEEuvBGLEEEvGCBBAxzF'>@............@#>-;;'~;;->=#+..............................................%-;)FKAvLEuAzF);,#+........@#-~]^syBBCByxtABBCBxsrF~,%...&=-!{/rrr/FFF/F//r/F{';->#&....+*;)FrxBCBCBAxszrrrsyABACAys/;@.@-]^stCCBCAAxszzstBCACAAAsrF)=+.@;/sAACBACCCCCCCACBBCBCAAxrF~$+..........+#-;)FryBBtzF);,#+.....", +"....@**,-'{/IrrQ^^rI^/1~;-=#+.....@%*=-~1/QQQIKONSNwJPIrQ[2(!>%.......&$=-!(/IrKKzr^/F(;-=*%@.......@%>-;|2[rIrKKKKI^F]);,$%+............%$=-;~)~;-,*#+..........@*-)F^IzsKK^F|)!!([PJJKKr/{!>&..+#=;_25MNNwJJJwNNwwJKzQ/F{~-#............+&*=,;!!!;;->#&+.............................................+@==;!:$&+.......@**-a:<5>#&+............#-9(2<8OJ8[:(_~~_1<5IKQQ[|;,%...@=-c(}56O68OYNNNSOPIQ[2|(;-%+...........+&*,;!(1:1~;=*$&..............................................@$,-a|;(1[Q5IIIQ[[[}:[[[QQQQ[}(;>..+$;|}[-!(1QI=3~a|:e2f1d_0->>#&.......+%$,0eiqqWWWWUTTTTTWqWWqjibc$.......@*=9_ek9afh5lnoqWUTTUqmhk7ebb9-%...........@%#>9ad:777ea;=#%+............................................+@*,;dfiqTUqpgdc-*%+........@&$>30_b4hhh<77khhh4bdc-=&+...+%=;d7hhhhk74ff7ijji7bda9=$&....%>0bfhhllh5lkh777hh..+>dekhllmmlklkhlklllhlhlhk4b0$..+*cd:7khhlhlkkhkhh9dioonlhkllVk[edc9>%@................+@&&&@@+.............+#3ab7mZWWqmhfbdbbekhVVVll4d3#..+&*-9dehlVnZWUTTTUWnVlk441a3#+..........@#>9~beklVlkea3$%@............................................+&>3abgiqTTWp7ea9>#&........@%#=9!beklVVllllVVlkeda9=*+...&=,0|kllVVVVhkkhXqZjl741ac>#+...%-aekVVVVXlVVVVVlVVVVVlllked,+.+,bklVVXlXXXVVXVXXVVXV6Vlll7b>+..=0b4kVVXVVVVllllVVXVlllk4bc3%.........&#>-ab4kmoZZohbc>*%+.....", +"......+&$-;_{F^zzr^]{~;>=%@.......&$-~FKGLEEuEEEEEuEEEEEEvvAk(>......+*-!]rxBCACBCCCByz^{'->&+......&=-!2JDvGBBCCCCAsz/);-*%................+@@@&@+..............%,!]rsHvELvHxsz^rrsACCCCCAs^'$...%,;)/ztACHvEEEEEEEDHCBxsr]~,@..........&=-)FrsABCCtzF~->&............................................%$-~FrxGDEEEGxzF);,*@.......@#=-~]^stBCCCCCBCBBxs^{~-=@...%-!]ryCCCCCCCACHvDEGHAAsr])-%..+$;FzxBCCCCCCBCCBCBCCCCCCByzF;@.@;/xBCBCCBCCCCGHHHHBCBCCBCBy^;@.@,(^zxCBCCCCCBBCBCHHHBCCAsrF'*.......&%>-')/zACHLLEDxQ{;-#@.....", +"........@#,;;){F/F])';-$%&........@$-!{IxtBHGGLuEuEEEvGHGGGs^)=+......#-;)rsABBABBABAsrF)!-#&.......&=-!FztBABBABBAyz/{);=%......................................@$!{/stHvGGAyzrr^rsAACBCCBs/)$+..+$-!]rsytCHGDuEEELvCCAAsr]!>@..........&$-)FzyBCHHBzF~;=#+................+@&&@+....................+%>;']rsyGDGGAszF~;-*&.......+#=;~{/zyBCACACBCCByz/]~-$&...%-!)^yACBCGHCCBCHGvvvHHBs/{!*+.+>;]/syABACBCBCCCCCCBCCCGCAz/;@.@-FzxBBBCBCCCBCBHCHCCCCCCCCAz!@.+-)FrstBACBCCCvCGHBBGGHCCyrF!*.......%,;;)]/sACHLvLDy/{;,*+.....", +".........+%*=,-;---=>#&+...........%*=-~1:}}[2$@+.......+#*>;d:2[////FF]_!-=*&+......................................+%>;_:QKPJKI[2{(((]/QKKKKI/~-%....@#=-;){][[IJwNwNwJKK^/F|!-%...........+&*-~][PwwwK['-=%@................+@@&&&@+...................+%*>-'1}[4[[<[2]'-=$%@........+%=-;){FQrKzKzKKrQF]_;-=%+...@#=;'{F[IJwwJKKKIPPOwwwJQ:'-%...#=-;){FF/2/Q^QKKKKKKPwwwOK}(=+.+*;~]FF//QrKKKKKKKKII/QIIJOP}-...%-;')]F/2^QPJwwwJJPwwwwJQ1~-%......+&=-;!)1/IKJOSO8Q(;>$&......", +"...........&&%*****%&@+.............&*=,3c!_(12-c_((||:((!;==$#+........................................%$-;(}QIIQ[}|(__(_:}[III[:_-&.....@%$>,;!_12<6SNYOPIQ[}|(;>#+...........&*>;_1Q6O65|;>%&................+&######&+..................+&**-;~((||||1(_'->*#+.........@*=;!_:[QQ,;_(:QKOOPIQQI..+%,-!__|:}[QIQQQ[Q[Q[22[<885|>+..@%*--'_(|:[-'(::[Q<<555[1~->*%+.....", +"............+@%%&&&++...............+&$>=,90abfgpqjpgbd093-=*&........+@#$,3c_____0_cc,*#@+...........+#$>99aa_0dac3-$*&&.........................................&$,cdfhhlh7f1bdddde47hlhhed,%......@%*=-30dfgjUUUqplk4fbd0>%............&$=30dfgigfd9>&+................+&$*$*>$*&+..................@#*=9;aaaaaaaac3=$#&.........+%=,;ab7gmmmmpmmii7bac3*%+...+&=39abbginXmlhhhkimWUWqifd9#...+%*=,;0adbe47hhmlikhijWWqpf0>...&=9adb:f47hh77774477777hmigb$...@#$>,30d:f7imjnpmiljWUqjgbc,&.....@%*-cb47hhhhh7efbbc9=**+.....", +"..............+@+.+..................@%*>99adbehmnnmkfda93=*#@.........&#**>999c9cc99,$*%@.............@*==--c9c3-9,$*%@..........................................%*3c|7lVVllk742ee47hVVmXlkb0*.......@#$,90bfioWUUWnVlk7e|a9#+...........@%$,c0abbbac,*&................@*>,9c0c93>*%+................+&%$=33393!93-9>$#@@.........@*=0d:7inqZWZZWqZXi7ed03#+...@#>30d|4klmXVXVllllnWUUqp7bc$...@&#*3cad|ekhlVVVVVlVnZWUZpgb,+.+%-9_b4kklVhlk7e4f4kklVVllkgd=+..&#*>3c!b4klVmXXVllXoWUWjhea9#....+&*>cb4hVlVVVl[4eedac3>*#+....", +".....................................%>-3;!)FrstCCCAyz/]~;-,$%.........+%#=>--;;;;;;--$#&@.............+%$---;;;;-->=#&...........................................*-'{rxBCCCCCBtyyxABBHHGGGtz{-.......&==;!)/zxvEEEEDHCAysr]~>@...........@#=,c!a_a09c3=#...............@=-;)Frzr/{';>*@................@&*>--;;;;;;-->=*&+........@%-;]/zxGvEEuEEEEELHszr]'-@...%=-~]^zsyCCHBCCCCBHvEEEEGyr{-...@#=-;(FrzstBCBCBCCCHHLEEEGs^;@.+=;~FzxACCCCAxszzzsxACCBCAxz],+..&=,-!)FrxBCCCBCHCHBvEEEDAs2)*....@$-'{ryBCCBCCAysszr/{~!;-*@...", +"....................................+%=-;;!)]^sABBCBxz/]~;;,=&+.........+&&#$---->>$$%%++...............+%%#*$$=>-=*&+...........................................+*-;{^yCCCBCCABBABBCCBLLLGAzF,+......&*,-!)FIyGLEELvCCBAsrF',@............+%=,-;!;;--$#@+.............+#>;)Frytyz/)!-=#.................@%#*,>,-,->>$##&+.........+%>!(/zstvLEEuEEELLtxz/]!>&...&>;~]/zsyACBCCCBCCBGLEELtsr{=+...@$-!{FrzyBCCCCCCCCBHLLuLGyr;@.+$;)FryBACBAAszzrzzyBBCCBByz]-+.+%$-;~]^zyACCCCCCCCCvLELGts/)$+...+=-~]ryBCCCCCBtszzr//])~;-*...", +".....................................&#*>--!_(FQIKKI/}_~;->*%@............++@@&@%%&+++....................++.+++@&&@..............................................%=,;)/IJJsKzKrrrrrKJJwwwOP}~=.......&#*>-;):Q6NNwOJJKr^/1);*...............+&%**##%&@+...............@#=-;(/KKK/|';,*%...................++&&@&&&@++.............+&%=-!(2<6OwwOwOwOJ<:(~;-&+...+#=-~(F[QKKzKKKKKKKJwwwO<2(!*.....&=,;)]F/IrKKrIrIIKPwwNw6[|-+.+*,;)F[rrIrI/F|{(]:[IPPKIr}];*...@%=,;'|F^IKKrIIKrKKwwNwO<}_;%.....%=-'{/IKKKrI^/}]:F}F|)!;,#...", +"....................................+@#>-;!!(1}}Q[Q221(~!;-,*@.................+..................................................................................&*=;_}QPPKIKIQ[[[[IKJwwSO<|!*+.....@&#$,-!_|46RNNYJP-~14P66<:~;-=%+......................+....................+%*=-c~12<[<<<[<421~;-,*&.....&,;_:}[[QIIIQ[[<866665<}(!,%.....@*,;~1}[QQQQQQQQQIKOSNR84_,+..%-!_:[QQQ[21__~(|}[QIIQQ}:_;#...@#>-!(}}[Q[QQ[QQQ,93caddd0daaa;9,=*%@....+#>cb4khllhhk777iqqomh7eb03=&....+&*-0b44hkkhhhhkhhhmoWUqpgb,+.+%9abf;df7kkk4k7k7khhmjWTUjgfd9%.....@*,;dekhlhk7f:ebef7kebd03*...", +"...................................+&$,cd:44[kkhkllkkk4ee:da3#+..................................................................................................+%*,cb4lXVXoZZqZoonXXZZWWZphb,+....+%$,cabe7hmqUTUZoXXmVlhea,@.......................................&$>9dehpWUWoi4dc3*&.............................................&#%>=99990c0c399,>*$%@+....&$9d4hVlVVVVlkhhXoWomhk4ba3$&....+#=9_ekVVlklhllhlVlnZWUWol49@..*cd:kllVh4:bddbe4hlllllkkeb0*..+&*3ceklllIlhkklkhVXZWTUqm7e0*.....&*3!b[lVl6VVhk47kIllk4eda>@..", +"..................................+%>-~]rsytBACCCACBBBAyysr/)-%..................................................................................................@=-'{^sBHHGLLEELDvvHBvLEEDHtr;@...+%=-~]/zxAGvLEEEELGvGGGHs/!#......................................+#-;)/stvEEEDBsr{!-=@............................................+%#=3-cc00~0'0;;3-=*@.....+*-)FstCCCCBCBBABGvLGAysz/);>&....%>;~/sABBCABACBCBCHGEEELvAs)@.@-(^zxBCBxzr//F/zxACCCBCAtyr(-...#-']ztBCAABAAtACACHGEEEDHxz{=....+#,!]rxCCCCBCCBAAACCCBxs/];%..", +"..................................+*>;)/stCCCCBCBCCCCCCCBCxz]!$..................................................................................................@$-!)rxCBHGGGvGvGHHABBHHGHxsF;+...+%=;)/rsAHLLEEEELLLLLLLGx^~*......................................@#>!{/sBvvELGHsr]'-=&.............................................++%$,>----;->--$*%@+......*;~FzABBCBBBAtyAAHGtysr/]~-$+...+%,;~FstAAAyyAtBABBABLLLLvAz)&.+,{/zyAAAsr/]]F^zyCCBCBCCCtsF,+.+%>;]ryAAAyysssyxAABvLLLvCyr(=+...+#=;)^stCCCCCCBBBBCBCAysrF;#..", +"...................................%*,!|5JwwwwwwwwwwwwwwwOK[(-%..................................................................................................+&*-;)}rPJKP5;)|}QJwNNNuNwNNNNwwO<|;&.......................................%*-~1[IOwNwOK}(!-*#+.................................................&@@&#&#%&@@++.........@$-;(F^^^//FFFF2[[[2|)~;-=%@.....@#=-')FFF]]]]]]]]/2IJwNwJQ1-@..*;')]]]]{!;;;;_:QJJJKIKKJPQ($...@*=;)FFFF]]))){]FFQPwNwJI[]!*....+&#=;)F/rIKsKKIrIKKzKI/F(~,+..", +"...................................@$=9|IOOYYwYwYwSwOwYOOM84_-#...................................................................................................&$=-_:QQI#+.....+&*=;_|[5OwwSNNNwwwSwR84~,&.......................................&$,;(}<8RSR6[:~-=$%+......................................................+................+&=-!(::(__~___(||(~;-,=*#%+.....+&#=-!!(_';;-;;!!_145OYO52(-+.+&=-;;;!;--==,-c([6OPQQQ<865_$+..+&*-;~__!;;;;-;;~(}<6wY8<:~-&.....+&#=-_11[%...................................................................................................@#=3a:7hlh7fbd0c033->>===#%+.......&*>3cafgijjjjjjjjpjppgfc=+.......................................@$>;af7ijqjigb0-*%&+.......................................................................+&*,;00_a9,390000a0;3=>$#&&.......&%$,99cc9-,=,,-3cabgimigea$...&*>,3,-,,>**>-3afgii7f47iiga#....&#>390099>>-=,-90bgiipgfa03&.....+@%>,;adbgjqZqqWqZqjgba9,%...", +"...................................+%$,3dbffg777g7gg7g7gfgba3=&...................................................................................................@*>-aehlVh4eb09,>*%&%&&&&&.........@#$,-0affg7g7g7g7ggffb09*........................................@%>3db4hmpihedc->#&.........................................................................&#>>999999,9,3,;99,>>*&@+.........&*=--9,->>>*$>-90cdfffba3*+..+**=>>$=**$$=*33abfbebbfbbd3#....+%*,93>-=>**>=,330abffba03=@......@#$=30aehoZWWZWWWZoibc9>#+..", +"...................................&*=39ad12F4///F///[F::1dac>%..................................................................................................+%=-!]rytBxr/]_9,#%+................@#=-9'_1]}/[//F2FF1:(d!9$+.......................................%=-!]/zsxtys^]~;=*&........................................................................+&#=>-;;;;------;--,==*&++.........@#$--->===>===--;~a(b{(!3%....%*==-=,==**=,9ca_|{]]{|1da,%....+%*=>--==>=*=,-;;'ad(1{_';-&......@#=-;_(FIyGHHHHHGCxKF~;,#...", +"...................................+&#=-;!~){())))))({))~~;--#+...................................................................................................&*,;)FrszzF{~;-*%...................+&$-;;!~~'~)))))~)~~;;-#+......................................+&#>;)F/zsysr/);-$&+..........................................................................+%$>,>->,=,=>$$$*%%%+.............+%*#*#$**=$**=,--;;';;;>&....+&%*%$##%&%#*=,-;;''''!!!->@.....+&%#**%%@&&#%$->-;;!;;;-,#+.......+%>,;')F/rrrzzrrr^])'-*+...", +".....................................+@%*>==-->===,-->>,===*&+....................................................................................................@%#=-'){{)'->=*++.....................+@%#==,>=>>=,,>,>=#%&+........................................+@#=-;'({]1)'-=*&+.............................................................................+@@@&&&@&@&&+.++.+...................+@@@@&&@@#*&%*$*%%@........+.++.+....@%#*$*#***#$#@...........+++...+.+&%&%*$*%%&&+.........+&%#$>3;;!;!!c!;;-==#@....", +".......................................+@@&&#%**#*%%%##%#%&@@.....................................................................................................+@&#=-';!!;=$%@+........................++@%##$*%$##%%%%@.............................................+%*$--;;;-==%@......................................................................................+.....................................+++++...+......................+&@+@++.+++......................+.++..+................+@%#*=>=======**&@.....", +".........................................+++@@@@@&@@@@@@++...........................................................................................................@%*,,,=*%&++............................+@@@@+@@+...+...............................................+&&%%#%*%&%@..............................................................................................................................................................+.......................................................+@&&%%##%%%%@@+......", +"...............................................+..++...................................................................................................................++@@@++.+...........................................................................................+++++.++..........................................................................................................................................................................................................................+.+.......+........", +"..........+++@++++.............................@@@+@@@+@+....................++++@@++.........................+++++++..................................................++@@&&&@@&@@++...........................+.......................................................+@+@@@@@@++............................+++@@@++....................+++&&&@++.....................................................+++++++................................+++++++++..................++&#$$$#&++..........................................", +".......+&*>=-,-->$*&+........................@#*$=>----=*#+...............+%*$------>*%+................@&%%#*$$>>>>$*#%@..............................................+@#=,-;!;;-,>$#%+.....................@+@@&@@@@@@&&@...........................................+##>>,--,==$*&.........................+%*>>,-,>>$#@................&*>--!;--=*#@................@&&&%%%##%%%%%@+...............+%*==----=*#%@.........................+&#*$---->>*#&@.............+%$>-)~))~;>$*#&+.............................+........", +".......+#*-;!~';-=$%@+......................+@%=,-;;~'~;**@...............+*=-;!)));-=*@...............+&%#*$=,>>>>>,=$##@..............+++++++++@@++++...............+&%=-;'))({)!->$&@...................+@%%&&%%%%&%&&&&++..............++@@@@&&@@+@&@+............+%*,;'~!;-,=*#+........................&#*--!~;--=$#+..............+%=-;')~!--=*%+...............@&%&#$%%#*#$##*&+..............+#*,-!~';-***&+........................+%*=>;!';;-=$%@.............@#,-!)]1|)';=*#%+................++++@++.++@@@+........", +".......&*=-;~(1_;-=$#@......................@%$>-;;((1~;=#@..............@#*>;!((||_;-*#&..............+&$$=-;;;;;;;-,=*%&..............+@&&%%%%%&&%&&&@..............+&#>-!(|221|';>*#+..................+&##*#*#**##$####%@.............+@%##**##*##%%%&+...........@#*-!(1(~;-->*&......................+&%$=-;((_!-,*%&.............@%*>;_(||(!-,>*&+.............&&%#$$*>>$*$*>$*#%@+............&*>-;~|(~-=*$#%+.....................@&%#*=-;(1(;-,=#@............@#>-;~|}[[:_!-=*%@..............+@&%%&&&&&&%&&&&&.......", +"......@%$=90deeba;,*#%+...................+@#*=3ca_bbedc,*%+............+&$=30abeeebc9=*%+............@%#*>3;aaaaaaaa;3>*#@............+&&##***######%%&+.............@%*,;abe474edc3*#@.................@%*=>>>=>>,,=>>==>>#+..........+@%#**>>==>=>==*$$&+..........@#*-abeb_a0;,>*@+...................+%#*>,;abeba;3>#&+...........@&*>3abeeebda3>$%+............+%#$=3-3-3>3-33,,=>#&+..........@%*=3ade:dc9=$*#@+...................+&%#$>3cdbeb_c3=#@...........+%*,;ade77kfbd03=*@.............@%%%#$*#%%#%####&%@+.....", +"......@$=,;de4k4bdcc,*%+.................+&*=,ca1ee444:a3>%+...........+&$=9~b}7khk[b~c,$#@..........+%=,99dbe44eeee:bac,=#&..........+#*=>,-3-,,==,,-=$*@...........+&*>3_b2khlhk4(c3*#@..............+%*,30caaaaaaadaaaacc>#........++&#=>3ccaaaaaaac93,,=#+.......@%$,c(4444e|dc3=*&+................+&**,9ca(ekk4|a9=>#@..........&#>-9a{4khkke|d93=*&..........@%*>-ca1bd~aa_dbdaa93=#+........+&#>3;de[7eb~ac,>*&+..................&%$>3;a(e4k4ea;3=&..........+&*,9a14khllk41d03=#+...........&*=>,,,,=*>>,,3-==>*#@....", +".....+&>;;)FsxBAxsr/{;-*@...............+#=-')FrsAAtysr]!->&..........@$>;~]rsABBCCAsr]~-=*+........+%>;)]/zxACBHHtAAyr/);,*+........@#-;~){]FF{~~){]F{;-=&.........+&=-;{^sxCACBtyrF);=#+...........+&*>;){/^rrrrzrrrzrr^/F)-@....+&%$$--!~{F^^rrrr^/F]]{)!,%.......%=-'{/syAtAxz/{);-$%+.............@#=-;~]rzxyBByr]);-=*+.......+%=-;)]rsyCACAAyrF);-*@........@#=-!)/zyyzzrrzsxsr^F{!,#@.......&$,-~)/stBAssrF);->#&+..............+%=,;!{/zsxAAyz/{'-#+.......+@#=;~FrsACCCCCBxzF)!-#@........@#,;~){{]])!!~)]]]{)~;-=&...", +"......#--!)/zAABBAsrF)-=&...............%=-;)F^stBAByz/{'-=%+........&*>-!{/sBACCCBBAz/);-*&.......@%*-)]rzyHvLLLLLHCAsr]~-=%........&=-'{F^rzr/]{]/zz/);,*&........@*=-~]rxBABBBBys/{;-#&...........@%=-!]/rzzzzssssszszzz/];#....@#=,-;~)]/rzzzzzzzzr/FFF)!>+.....@#-;'{/zyABCByz/]~;-=#@.........@&##$-;!]^stBBABxrF);-,*&......@#=,;'{rstBACCCAByr]~;>*&@.....+&=-!)FryttyszsxyAxsr/F)-=%......+&*=;'{/zACBBAxzF);-,#&@...........+@#*--!)/sytBBByrF);-=&.......@*=-')^sBAACACBCByr]~;-#@......+&>;']F/rr/F{)]//r//F]~;>#...", +"......&*,-;)]^rrIr/F_;=*&..............@%*,-!)]/rKzr/]~;-*&+........+&#=,-~{/rKzKzKr/}{!-*%+.......@#*=;~]}QPwwwNwwOJI/|!-=&@.......+&*,;!)]}/}])')]}F(;-*%+.......+&$=-;)]^rrr/^^FF(;,$%@...........@%$>-']}/[Q[/^Q/[[^[/}:_;&....@#*,--'~(]}/[/[/[/22F:|();*......@#>,;'(]F^KzK/F|~;-==*%+.......+&#*$>--;)]/rrrI^/|);->$%@......@&*=-;~]/rIzKzKKr/]_;-=$&@.....@#>,;~(]^r^^/}//rr^/F|)',*&.......@#$=-')F/rrIr/})!-=>$#&+..........@%**,-;)]/rrrr//(~;=*%+.......@#*=-~{/IzKrKzIzI/F~;>=*@......@&*,;'_{]2]|)~){F2F{_~;>*&...", +".....+%>>;!_|2}[[[[1_-,*&+............+&$>-;~(|}[[Q}|_;-$#&+........+@#>-!~|}QQQQQ[[2:~;=$&+.......+&$,;_(:[;!(|22}|((((||);=$%+.......@&*>-;_12[Q[Q[21(~;$%@+...........@%*>c(}<<55555<555<555@....+%$>;;__:}}QQQQ}|(!;-->*&.......@#*$-;;!~(1}[Q[[21(~!;,*%@.....+&$>-;'(1}[Q[QQQQQ2|(~;-*#@.....&*=-'(|1[[[[}2}[[[[}1|_;,$%+......+#*,;;_|}}QQQ[21~;;,=*#@.........+&#$>-;!(1[[QQ[2:(!-->#@.......&*>-;~(}[QQ[[[QQQ[|~;->*@......@#$>;_(|:}}1(((|:}::(_;-=&...", +"....+&#>3cddb:f7k77fb0-$*+............@%>9cabbe4777fda-=$%@.........@%*3;dbef4k47444f1dc,$%+.......@&$,;dee47hhihihh4ed0-*#%+.......@*=3cadee77fbbbbbba;=*%+.......&#=3cad1f4<7<47bdc9=%&+...........@#*9agiqqqUqUqWqqqqqqUqgb*..+&#>9adbfijjqqqqqqqqqqqjpgfd,&...@&*-;aab:f477hk774ebdac9=#+.....@&$,3;adbbe74h<474fbbda9>*&.....@#,9!db:7477447k7h7febac,*&....+%=3;0bee4h4h44e7777eeedc-=%+.....+@$$-cdbbe44k774fbda;3=*#+.......+&#=-30abb74*@.....+@#=-0_b:477[7444h77ebba3=&.....+&$>-0dbef77feeee474ebba;=#...", +"....@%=3adee44kcd1e7Illlhk4k7eb03=#@......+@$=3ae4khhklVVVVh7edc3>#&+......+%*,cd:e4klkk[444e1dc,$%@......@%=30d144khlVVVh71a;>*%@...........@*,3agoWUTTTTTTUUWWUTTTjb>..@#>3~b47hpZWWWWUUUUUUWWZnXhea*..+#>3abe47kkhhhllVlhh774eda,#+...@*>-adee47kklllllhhkk74edc3$@...+#3cde47khlllhhkllllkk74ba3$+...&#,cde4khllllllh5lllhh471a9=%....+&*=30d|e44k;)]rzxACCCCCBByrF);-$&.......&=-!]rsyBCCCCCCCCBsr]~-=&.......#,-)/sABCCBCCCCCCAzr]~->%@......%=-~]rsxBCBCBCCCByz/{!-*&......%-;)FzsABCBCCGCByz^{',$%+..........&-;(/stGLEEEEvHBCCBAHBAx/-@+#-~FPtvLGGCHBAyyxAtCCCCCCCAs/,++*-'FsGGLLLLELLLLLLLLEELutzF),&.+*;'/sGGLLuLLLLLLLLLEuEEExr]!=+.+*;)/JGGLLLLELLLLLLLLEEEvwzF)=+.+$;{/JGLLLDELLLGLLLLEEuEDwr{;*+...&=-']/zsABBCCCHCCBBAxsz^]!,%....@#,;)FrzyxBCCCCCCCCBysz/{!-#....#=;~FzsABCCCCCBCCCCCAAxzF)-%...+#>;)FzyBBCCCCCCCCCCCBtyzF),&..", +"....@*-!{}/IKJJwwwwOI}_'-=&.........&$=;(F[IKJwwwwOPQ}(',=#+.......@%=-'][rKJJwwwwOPI[{;->*@.......@*=-)/QrKJwwwwwOPI2{~;=$&.......@*>-)]/QKJwwwwOJKIF{~->#@......@*-!(}/IKJwwwwwJI}{!-*&@...........@*,;~2QPONNNwJKI/[[[rIK/(=++&=-)[8wwwJKI/}]1|1:[QrKKIIr})*.+&>;_[6wwNwNNNwwwNwwNNNNS6}(;=@..&,;_[8wwwwNNNwwwwwNNNNNS52~-%...&=;(QOwNwNNNNwwwwwwNNNNw5}_;%...%-':QJwwNNwNNwwwwwwNNNNw5{~-%....+%*,'{/[IKJJwwwwwwJKK^[]~-$@....+%*,!{F[QKJwwOwwwwJJI/[]~-$@....@#=-']/IKJJwwwwwwwJJKr/F_-$+....&#=-)]^rKJJwwOwwwwJJzI/F(;$+..", +"...+@$>;(}QI6YMNRNRY52(;,*&+........@%-;(2[<6YMNNMN65:(;,$%@.......+%$,'|}[I6OSSNNS8<1('-*#@.......@*=-!:[QKJMMNRNN6<1(~->&@.......@%*-'1}QIPOSYMNM8<:(!-=#@......@$>;(2[IPOMNNNMY5}(;,$#@...........+$>-a1[<8SNNS8&+........@#=;de4hjqWTTTTqjeba-=#&.......+&*,0b47hpoWUTTTqieba;>$@.......+%*3a|44ipqUTTTTjifba3>&@.......@%*3a:fhhjqWTTTTjifba9=#@......@*>cde4hmoWUTTTqigbc3$%@...........@*=9afgmjWUWqpi77fe47k7bc%..&>cdgpqWqmgeba99-90def7774fb3#..@*,cdfgg5i5mgggffggijUWqieac*@..&*-cdf7hhi5mi77fgggiqWWjiea9#...&$-9dfg5ilmmi77777giqWWqgfc9#...&=3abekhi5mmh77777gjqUWjiba-&....+&*>cdeehpjqUTTTTTqjikfb_3#......@*,;de4hmoqWUTTTqqji4fba,#.....@%*,0be7hpoqUTTTUWjmi7fba,#.....@%*3a|f7lpoqTTTTTqqph74b0,#...", +"....+&>3cbe7mooWUTTWjl71c3>#+......+@#>3!be7mjqWUTTUoi71c3=&+.......@*=;db4hmnoUTTTWmh71a3*#+......+@*,9db4kmoqUTTUqmk4103>&+......+%$,cde4hXnqTTTTqmk4(c3*&+.....@#*30df7lpoTTTTUjh4(03>%+.........@&$-0b4mooqZoonVVllkkkk4b;#..#3aehoWWZmhedc93339cbe4444ed9&..@%-;ab4khVVVI74ef}4ioWWqifd9=@..&*,cae7khlVlhkf44k#+.....+&*,;~]rztAvDEEuuGHxzF~;>%.......+$-;)FrstCvvEEuEGBtzF);>%+......&=-;(F^stHvLEEELHBxz]';$@......@*>-!{FrsAHvEEuuEGAyr]~->%+....@*>-~]rstHvDEuEuvHyzF);=%+.......+#=-~]rxDDDvGGHCCCCCCCtsz/'$+.*~FKADEEEtxrF)';;;'~]/rrrr/]!%..&$3_FrsBACCBAysssxBAGvLGxKF_-&..&=;_FzsACBCCAyssytAHvLLGyI{;$...&=9_FzyACBHHAxssxxBGvvvGyQ{;$...%-;)/zxtCCHGAxssxtBGvLvGsQ{;*.....&=-;~{/rzswvEEEEAxsK^F{!3*......+*,;~{F^zstGEEEvGysr^])!-*.....@%$-;){F^zstDEuEvAysI/])'-=@....@&$,;~)F^KxtuEEEEHyKr^]);-*+..", +".....@$-')]zyCCvLEELGHAs/)'-=&......%=>;'{/zyCGvLEDvvCBs/);-*&.......#=;~]/sACHDvELLvHAzF);-$&......@*-;!{/zABGvuELGGCAzF);,#......@#=-!)/zsBCvLEEEvGCtzF);,*@....&$*-;{/zyBGLEuEvvHAs/)!-=%+......@*-;)/KAvLvCCBCCBCCCCCBsrF'*.+>'/ztvLEuHsr]);;--;;)]F/^^F)-%..+$-!FrxABCCCAyssxACCCBCts^{!,@..@$-'FryAACCCAxssyBCCBCCAs/);*+..&=-)FryAACCBByssyBCCCBBszF);*+.+#>;)FzyABBCCAyssyBABCCtxzF);*+....+%=-;'{F^zstEEEuAsr/F)!;,%+......@#-;!){/zsHLEuDAsz^])!-,%+....+%#=-;')F/KyHLEuDAzr^])';,%+.....@#>-;;)]^zyGEEuHAsz/]);;,%...", +".....+#>-;_1QJJJP6P6JOPQ|~-=*&......+%*>-!_2*@......@#*,-!)2KJJJP68JJJI[('-=%+.....+%$=-!):[KJJJ866JOJK}('-=$@....@%#=-;(2QJJJJ888JOKQ1';=*%@......&*>-;(:5JwwJsKsKKKKsKKr2{~-%..%-_:$*$$=-;;!!'!-$+...&=-'1F/QIKK^/2F[rKKIQ^[{~-=%...+#=-'{F^rIKrQF2F[IKrIQ^}{!-=%...@$=;~{F^IKKKI/}/^QKIQ^[1(;-$&...@*>;_1/[rIKKQ/}2^IKIQ^2:);-#@......+&#==-;_(45wwNO<}(!;->$#+.........%$=-;;1[5MNNO<}(';-=*%+......+@%*>,-;~1[8wNw6<:_';-*#&........+&*>=-;_|[6wNw6[1)!;-=*&+...", +".....+&=;!_:<6O5<[4<8Y6<1~!3=#+.....@&$=;'_:58Y5-'(}<6O5<[Q56Y6[1~;-*&.....+&%$,;~(156O5<[Q<6O841~;-=@.....+&#>;!(1[6OO5<[[56Y5[(!;-*&....+&*>-!(1<8O6<[Q<6Y8<:('->#@......@#>3!_:Q6JPPIII6666665<:_;=@..#,!b[6MSO<:~;=*##%#$*=,>,=>>&+...%*,;__}[QQQ[}::}[QQQ221~-=&+....&*,;_:22QIQ[}11}[*#+...........@%$=-9_e5YRY5}_;-=*%+.....", +"....+%=-cdbfiqqmgffgpqjifbd03*@....+&#>3cdbgiqqpgffgjqjieba0,#+.....@%=-adbgpjqigffijqjifba0,#.....+&*=3adbgmqoige7ijqpiebd;,%......@*,;abegpqoig7fijqjgfba;,#+...@#=30dbfiqqpgffgpqoiebbc3=#+.....@*,;abb7imlliVmoqUWWqqifd9>@..%3cbgjWWqifa;,=$###*$>=>>=**@....%*-cdbe4hmhh4}f7hhh7feda3>&.....%*-cde44hmi74e44hlhhfed03$&....+%*-9db44lih4ff475ihfedac3*&+....&=,cde4hhli7fee4hlhfeda9-*&..........@#$>90biqUUjgb03=*#@............@**>30biqWqpfbc3>*%@...........@%#$>-0fiqUqjfa0-*%&............+&#=9;0fiqUqifdc,*%@......", +"...+&#>0de7hnoZph[7koZZnh7edc,&....+%*30be7hpZophkkinWZph4ed9>&....+#>3a(e7hnZZnh4hloWomh4edc$@....@#>9abekkpZqXhhhloZomk7ed9$+....+#=3_b47koWqmh7hmqZom74bd9$@...&*9c(e7hnoqnhkkhoZqnl72dc,*&....@*>9d:44lVlXVXXoZUUUUUqjgb0,&..#3aeioWWqp7bd03-,>,,3-3-3-,*%....&*,~b4klmonmlllhnnXh7:dc3=#+....&=-a|ehlmoomllhVnnmh4eda3=#+...@#>3_b4klXoomllhmonmkebac,>&....+%=9a1ekhnonmlllmnpl71bac-$%+.........@%*,cafiqUUjiba;,$%+............@%$,9dfiqWWjgba9=#%+............@#*390fpqUUjgb9,>#+.............@%$>0bfjWUWjgd;,>#.......", +"..+#,;~FzxAHGELvHABHDEDGCtxz]~=...+#=;~FzxCCGELvGBAHDEvGCtyr];$...+#,;)/zAAHGEvvCAAGvLvGAAsr];#...+#,;)/syCHvELvHAHHDLDHCtsr{;%...+#-;{^stBGvEEvHtAvLLvHAAsr{;%..&=-)FzyBHGDEDHBBHvEvGBAyzF)3$+..+*-']rstBBHGGGGHGDEEEvDvts/{;%..*_FztGELDHssr/{)~~){{{]]{{);>+...*-!{^stHvLLvGCCGvvvtszF{~;>&...@$-~FzstGvuEvHCGHvDvtsr/]~;=&...&=-~FrstHvLLvHBHvvvGxz^]);-=@...@=-~FzsAHGLLGACCGDDGyz^]);,=@.........@*,;(/KtEEEDyI]~-=*@...........+&=,;)}ztEEEGJr{~-=*@............&>,;(FKtuEEvJ/(;-*+.............&$3;(}JtuEvvs});-$+......", +"..@#-;)^sBCBGLLLHCCHLLLHCCAs/)>+..&$-;]^yBCCGLEvHCCHLLLHCBts/)>+..+*-~]rxBCHvLLvCCHGLELHCCtsF!*...&$-!]rxBCCLLLvHHGGLLGCCBtzF'=...&=-~]zyBCHLLEvBCHGLEGCCBtzF'$++%>!)/sACBvLLLHCBCLLLGCBAs^)!>&..&$;)FztBCHLLLLLLvGHCHHHAyr]),%..*;FIyGLLLHAszrF]]]FF/rrrr/F),&..+*-!]^syAGLLvBCCGvLvAsr/]~;-*+..+*;~FrxAAGLLvCCBGvLGysr/]);>%...&=-~]zsAAGLLGCCBGLDGyzrF)~;,%...%=;~FryxHvvLvCCBGLLtxz/F)~;-#.........@$-;)/KAuEEDxr]!;=#+............&*-;)FPALEEvy^{'->$@............&*,!)/JALEvGs^);-$+.............@$-;)/VALuuGs/);-$&......", +"..+&=-!{/IKPwwNwwJKJwNwJJKr/|;*...@#=-!]/IKJwwNwJJKJwNwJJzQF(;*...+%=-!]/IKJwwNwJJJJwwwJJKr});%...&*>-~FQrPJwwNwJJJJwNwJKKrF_;#...&*,;'FQKKJwwNwJJJwwNwJsK/F)-%..@*-'{/IzJwwwwwJJJwwwJJzr}{!-*@..&*=;(/rIKwwNwNuNwJPKQIQ[2(~;$+..@$;([6wwwJIQ}]|)~)1]}}}/2F_'>+...&=-!)F[IJwwJKKKJwwO<}]_~;->&...+&=-'1/[KJwwJPKPJwwOQ}:_~;-=&...@*>-~12QIJwwJPKKJwO8Q}|)~;-=&...@#>-~]2QIOwwJKKPJwwPQ:{(!;,*&..........@*-;_e5wNwOQ|'->#@.............@&*,;~}5wNwJ<|!-=*%@............@%*,;~26wwwJ[|;-=%@..............&=--)26wNw6[(;,>#@......", +"...&$,;(}QI5OSSOJPIPYSSOK#+...&*-;(:2<8OO6III6YY6[1(__;3=%...+%=-~_:[<8YO6I<<8OY6}1((~;-=#...@%>-!(:}<8YO8III6OY54:(_~;-=%..........&*=-c|5YSR84(;-=#&.............@%$=9_|#&+...........+@#$=-a:5YSY84(;-$$@+............+&*>-a15YSR5e_;,*%&......", +"..+&=3cb7hmmjZqommlmjqqoplh7d9*...@*=9ab4hmmoqqqpmmpoqoXmlhfbc*....#=-cbkhmmoWqomlmmqWqommhfd3%...@$=9af7immoqoopnmjqqZXmlhfdc$...@*=9aekimmoqWjmmpoqWopmlgfd9#..@$3cdfilpoqWopmmnqWWomlh7bc3*@..@*3cb7himjqWWUUqqjih77febba0$+...%>0d7hilpopmhk77khimjjjifbc$+...@*-cdefgpoomlkhmpqjgfbbdaa9*+..+&*3cdffhpoomihhmjqpgfbbdda3*...+%*-0de4hpoomihhmjqpgbbbbac3#...@%=3cde4ipoomhhimjqigfbbbaa-&........+@%*,9abiqWWjgb;3=$#+...........+&%>-cafiqUqjgd0->*%@...........+&#>-cafpqUqjg_c9=$&+...........@&*$3;dfiqTqjeb03=#%+.....", +"..@=-'b7inXoYZoXXVVVnZZoZomifd=+..@*-!b7mXoonZZnnXXXnoZooonlfd,+..@$3ab7mnnooZnnXVVXnoZoooXie0$...%=9~b4mXonZZoXXXXnoZoooonhfa=+..#>c_bhmnXoZZonXXXnnZXZnomkea*+.%-0d4iXooZZoXXVXnooqZooXi4dc,&..%30d7inXoooqoqqZoXVlk7k444e_9%..+%>9de4lmnZZoXVVVVVXZZWZph:a,@..+#,cdb4klXnXmlhklnopmhkk44ed,&..+*=9~b4klXnXVlhhmmnmVkk444e_=+..&*>9ae4hlXonVllhmnjmik7444e0$+..&$,0de4klXXXmhklmnomlk4444:a$........@*=33adgiWUUoiea03,=*@.........+@*>,30bfpqUUogedc3>=&+.........@#*>,0abgjUUWjged;3,$#@.........@&$=39abgjWUWjhbdc93*#@....", +".+*;)FzxHEEDDCCBCBCCHHGDLEDHs/'@.+$;)FzyvLLLLvHGCBCCBHvLvLDGs/;@..*-)/zyDEELvvvCBCCCCGvvLDDts/;@.+=!{/sADvEEvGCCBHCBCGGLvLDAsF;+.+=!{/stvEEvGGHHCBCHBvvLLLDtKF-@+$'FzxGvDEDvHCBCCCBGGDLEDGyrF!$++$'FzxGDDDvGHHGGGHBCCAxyyAAAz{=..+#-!]rstAGDLDHHCHHHHLEEvGyzF;%..%,'{^zxABHvBHtAttBHCCBBBABxr{*.+*-;(/zytBHGHBABtCCCCCBCCABxr;@.+*-!)/zxABCHHBttAtCHBCCBCBBy^;+.+*-~FrsyBCHGCCAxtACHHACCBAty/;+....+&#=;'){FrsHuEELtsrF{)'-$@.......&#>;!)]/rxGuEEvtK^]{~'-#+......+&$,;'{F/zsvuEEDAz^]{'!-=@......+%=-;~)]/zyGEEEvxzr/]);-$&...", +".+#;)/zyvLELLGCCCCCCCBGGLEuGxr;&.+$!(/KtLEEEGCCBCCCCCCBLEEDHxr~&.@$!{^KtLLELLCCCCCCCCBvLLuLGyr;@.+>;{/stLLLLLCCCCBCCCBCLEEuGs^;@.+>;]/KGLEELLCCBCCCCCCGLEELHs/;@+*)^zyvLELLCCCCCCCCCCGELuvxzF)$++=)/zAGLELGBBCBCBGCCABAtABCCsF-+.+%-!{rzyAHGGGGCGvBBCGGvvtsr]-&..#-']rsACCBGGCAtyABCCBCCCCCAs{>++%;~]rsACCBCCCBtytBBCCCBCCAAr~&..*;)]rsACCBHHCBAxBCCCCCCCCBAr;&.+#;)FzxCCCBHCAtyABCBCCCCCCCxr;@....&*=;!{F/rzxHLEuEBxzr/])!-#......&#>-~{F/rKyGEEuLByz^/]);,#......+*>;!)]^KsxGLEEGtsr/F]);>#......@=>;~]F/zzxvLELuAszr/]);=#...", +".+%>;(2....%$-')]}QIPJwwwwJJKPI5<21_-$+..&>;)}IPJwwwwJIrIrIKKJJJJKzQF'*.+&,-~}'1[5MNNYOPQQ2}2[QIPRNNR6<|,+..%-~|[5MNNSOKQQ2}}[QIORNNR8[|-+.%-(}-9~|:[QPJJJPQ[}:(_0,$#+...%,9_25OYwSYw8PKIIIIPOwwO6<[|!*..%=;_26OYYSSY8PPPIKPKOwYOPI[(-@..&=3_48OSYYwYJ5KIIIPKOYwO6<[)>+..#=9(48OSMSNY8PKIIIPJOwwOP<[(=....+%$>-_(}}[Q58SNNR8IQ[}1(;-#+.....&#>;_|2}[<58SNNR6;_|:[Q<5MNNNM6;_|}[[Q6MSNNY6QQ}}1(;=&...", +"..%>cbfiqUTUqmlkfee4kimqUTUqif9@..%,cb7iqUTUjmkkfee4hhmqTTUjie3+.+%3ae7mqUTUomh74ef7hlpqTTUjhf-+..#-0bgjUTUUjpk7eee4hloqTTUjgb3+..*9afhpWTTqomh4eee4hloqTTUjgb,@+%cd7iqUUUqmkkfef4hhpqUTUqigd3%+.#;bgiqTTWjmmjWUUUUWWopnpjWqid>.....+&#$,cabfhmmVmikfbac3>*&.....@>;dgjWWUUUWopmmmpmnqWWZni71c*..%,0bgjWWUUUWqmmmmmppqWWZjl7b3+..&=cbgqWWUUUWqnlmmmnoZWWZni4b>+..#,0bgqWWUUUqopnpmppoqWWZph4d>....+&*,;defkh5mqTTTTjmkh74ba-*@.....@*=9de4khlpqTTTqjlhh4fba-#.....+%*,;de47klpqTTTqjlhk74ba-#......&*,cde47klpqTTTqplh77eba3#...", +"..%,abhmqUUUqXll7444lVXZUTUqm7c@.+#,a:hXqUTUZnVh444kllnWTTUoVec@..#3_ehpZTTUnXVk74kklVnWUTWoie9+.+%3d4ipWUTUZXVk447klXnWTUUom49@.+*;b4ljWTTWoXVk447hVXoUUUWome9+.*;e4moUUUZnVl7447lVXqUTUqmkbc#..*cehjqUUWZmmoWTUUUUWZnnXoUqjb,......@#$,cdeklVXXXlhed03=*&+.....%=9bgoWWUUUWZnnnXnnoZUWWoVhec#..%3abiZWUWUUWoXVXXnnZZUWWnmke9@.+&,;bioWWUUUWZnXXXnnoZWWZnVkb,+..%9cbioWUUUUZonnXnnnZWUUZoVkb,+...+#*3ab4khVlnqTTTUoXVlhk4dc=&....+%*3a:7kllVnqTTTWoXVlkke(9$+....@#>3ae7klVVXqTTTUoXVVkked3$+....+#=9_e7hlVmnqTTTZXVVVkkea,*+..", +".+*!]rstvEELDHCtysyxACHLEEEDHs)%.+=']zyHvEELDHBBsssxCHGDEEEGAs)&.+=!FzyHvEEEDHCAxsxtCCGDEELGAs~&.@=~FzAHDDEEvGCAssxtACGDEEEGtz!@.+='/ztvLEEEGBCtssxAAHvEEELvtz'%+=_rxAvvEEvGCtysxxBCGDEEEGHxr{=++>(rxGDEEEDGHvDEEDLEDGGGCGDDwe,+.....@#,c)/sxBHGvGHxzF~;,=@......%-_/xHvLEEEvGHCGCHCvvLLvGByr)*.+*;]IsGDLLLLLGCBCBCHvLELLGCxz'&.+*;_/xGvLDLvvvBCCCCCGvLLvvAy/-@.+*!1IyDLLLEELHBCCCCHvLELGGAs/-+..+&=-!{rxtACCHvEEEEvGBCBxs/)3%....&=-'{rstCCBGDEEEEGBCCBxs/)-%....&$-~FzxAACCGvEEELvCCCAyzF~-&....@$-~FztACCBGDEEEEvBCCAyzF~,@..", +"..$;)/zyAGHHBBAyszssABAAHBHtxz)&.+*;{/sxBACHACBxsssytAAAHHHtyr~&.+=;{/stAHGGCCtyszsttABBGHHBs^~@.+$!]rsxAHHHBBtxsssyAACAHAHts^'@.@>;]rstHHHHCBAysssyBACAGGAty^;@+$)^zyAHAHBCAysszyAABCAHHAxs^)$++>)^sxtHHBCBACCBCCHBCCBCBCHtsF-......@$-;)/sBHGLvvtsr{'->%+......&>!1ztCCCHGCCBCCCBCCCHHCAyz/'*+.#;)/sABCCCCHCCCCCCCCCCCCBxsF;@.+%;']zACCCHHCCCBCBCCCCBHCByzF-+.+*;)/stCBHCHBCCCCBCCCBHCBAyz]-+...@*-;)rzyABBACAHHHCABtyyzF),%....@%>;]/zyABCCBGGGHBCABxsrF)-&....+*-;]/syBAACHHGGHACAAxsrF',&....+*-~]rsttBBCAGGGACBBtysr]~>&..", +"..@=-_(:}[[[Q^/F]{]]F2[[[Q2}:_,+..&>;~(}2[[Q[^/F]|{]F/[[2[[2{_-...%>;~1:[[[[[^F]{(]{F[/[[[}2]~=+.+%>;_1:}[2[^^/F{{{]/[^[[[[2{~>..+#,;_11}[2[[^FF{{]]2[[[[[[}{~=+.%-~(:}[[Q[//F{){]F2[^[[[2]{',&..%-~1:}[[[[//[^QQQQIIrIrQ^[}_-%.......@*,;(2IJwwwJQ:_;-=%+.......+%-;(:}[Q[III^rrQ^Q/QQ[[/:(~>@..+*,;(2[/QIQQrQQ^Q/QQQQ[/2](!$...@=,;_:[[QQIIQ^Q^I/Q^QQ^[2F(;$...&*,!(2[/IIIIQrQrQ^QQQQQ[F:)!*.....@*,;~]]F/^[Q[[[[Q^/FF]_!-$+.....@#=;))]F/[^QQ[Q[Q^//F{(!-*+.....+#,;){]F/[^2Q[[Q^[/F]])!-#+.....@*-;)]]F/[Q[Q2QQQ//FF])!,&...", +"..+#,-!_(_(||(__!!!!~(((|((_!,*+..@%=3'~((((|(__!!!'__(|1((_!-%+..&*-;!___|||((';;!__((((|(~';*...@*-;!_((1(((_'!!!'_((((((_';*+..#*-;'_((1(((_'!!!__(|_||(_!-%.+@=-!~_(|(1__!;;!!~(_((((_';;$+..&>3;~_(||(__((({|||:::::|(_;=@.......@%*-'(}<668<:~;,$%@.........@*>;~)(|::::1111:|:||((_~;-#+..+%*-;~_(1|1:|:1]|1{|||__~';-%+...&#-;~(((::1::1:|1|1(|(__!;-#+..+#*-;~((11::11|1:||||||(_~;>%.....+&#=;;__(((||1|||1((~~'-=@.......@*>;''_((_||1||(((_~';-$&.......&#,;;__((|1|1|||(((_!;-#&......+&*--!__((|||1(|((((~~;-*&...", +"...&*>33cc0;0cc9-9939c90cccc9=&...+%$>,9900c0c99-333c9c909cc3=&...+&*,39cc;c0;9-3--9cc009ccc9,#....&*,33c90090c933333000c9c9-=&...@%=,39;9c09cc93339ccc0c0;c-#+..+*>339c090099--3999cc09093,=#+...#,-39cc0cccca00a0c~a_aac0c3%........+@#*9;abbffba3,>#&+..........%$,39c!a_aaaa0c'0a0!0cc93=&....@%$>;c9_aaaa~aa~c00000cc9-=&....@&=3-c0cc'a'a~a~0cc000c99,*@....&#>>;cc0aaaa~cc0'0000c099,=&......+%$=,330900~a0;0cccc-3=*&.......&*$=333c0c0~a000;9c;3,=#+.......@%$,339cc0c000aa0cc33,>&+.......&%>,3939c0a0000!;c993,>%@...", +"...+&#=,,3,3,3>->,=,3,9,3-3,,=&....@%*$>-,>-3,-,>,,,,3,-39,,>=&....@#$=,-,33>3-,>>==>>-3-3-,=*@....@#*=,,9>>-,,,,>,=,>>>--,3>*&...+&#*=,,9-,-3-=,==>,,-,3,3==%+...%*=,-,,-,>-,$>>,,9,,3-,,>**@....@#>>-,-,99399999999933-9,>=#+.......+%%*=3-00a003,**%+...........+%*>-933339;939333;93-,=**&....+%#$,>3-33;993999-3-3-,=>*#@....+&%*>--939-39c9-3--3>-,-=*#+....+&#>,,-3-3;999993--3,9>-=$%+.......+%*=>,,-33399939-,=>$*&........+&#$$=,-,-33993339,=>*#@.........&***,>-333-3333-9,,>$*&.........&#*=>-9--939,-33,-,>$%&....", +"...+@%$->---------,-------->-$&....@%=>=---,-----,>>-------->*@....+&**-,----,-,>,=>->-----,>#@....+&%$>----,---,----3----->>#+....+&#$>--------,-->------->=%....&**>------>,==>----------#%+....@#$>--->----;;;;;;;;;;;--,#@.........@&#=-;;!cc;;-*%&............+%$>--;;;;-;;;-;;,----,$#&@.....@#$,--;-;;;;;;;;-;-----==#@.....+%*>---;;;;;;;;-;-;---->#&......@#$>---;;;;;;;>-;-;>--->*#+.......+#$>-----;-;;-;----=#&+.........@#*=,------;;;---->=*%@.........+%#$>---;,;;;;--,-->##@.........+#*>-,--;;-;-;---->=*%@....", +".....+&%**%*%*%#%**#%#***#*#&@.....+&&%***$**#%#%%##%*$###%*%&+.....++&%##*#**%%#%##%****%**%@.......++&**$**%*%**%**$$**%%*#@......+.@&##$*%*%%%&%#**#**#**#&.....+%##$$***%%%%##%*#$*##%%%+.......@%%%*=*$*$#$*=$$>>====*%@...........+@#$==>>>>##&+..............+&%**$$===$=$*#$>*#*%%@++.......+&*%#$$***$==*#=$=$**%%@+........@%#*=====$*$$>$=#**%%&+........+&#**$*$=$##$=*#=$$*%%##@..........+@%%*#*$=***$$**%%@+...........@%#**$==$**$*#**%#@+++.........+.++%%##*$$$$$=$*%%%&+............&%%*$==$=$===$#%#%&+.....", +"...........+..++..+.+.+++.++............+.+.+.+++....++++...............+.+.++........+.+..................++.+..+.......++.............+....+..+....++++.+.............+..+.++.+..+.++++..................+.+.++.+..++++..................+++++++........................+++...++..++.++..............+...++.+.++.......+..............+++++......++..+++.................+.++...+...++.................+.++.++.+....++....................+..+.++..+....................++........++...................+..+++++.+.++.+........", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"..........................................+@&%%%**%%&++++..................@&%&@+...............................+@%%%@+.....................+@#****&+.......................+@&%%%%%%&++................................................................................................................+@%%#*%%@+.............................+@%%%%@+....................@%%****%@+............................................................+@%%&++........................................................................", +"...................++...................+&=-;!))~{)'!--,-=$&+..........+%*=-~);;--=*&+.......................%*=>;''!-,$%&+.............+%**=-~({~);->*&+..............+%*>>-;!))))'~!-->>*&+..................................................................................+++.+@&&@+.............+&*-;!')!;-,$%@+......................@%$,;;;~!-,%&+...............@#,!))())'-,*%@+...................+++..............................+%*>>;;';-=*%@................................................+%*****%&+...........", +"......@&%%&%##%%&%#%%%&@+.............+%#>-;))F/^//F])))'!--#@.........%>-;)]]]~)';--%+....................+%=>;!)]]])~-,*&+............&$-;!]/^//])~;-*%+............+%*-;!))(F/FFF]]))';-=%+...........+&%&#%%%#%&%%@+....................................................+@%#*###**==###@..........@$-;~]]]{)~'-,*@.....................+#>-;!)]]]~;-*%.............+#$-!)F/r/F])!-,*%@..............+&%##*%%%##%%%@.....................+&$-;!))))';-=*&...........@&%&&@+...........................+@#$>--;--*##&@........", +"......@&%$#%**#$*#*$#%%&@.............+#*=-;)_]}/}F]_()))!-=*@.........&*-;~{{1{)~;-=#+....................@%>=;;'{]]~'->$&............+@*=-;)F//}]{';-*&+............@%*=;!)(]1F}]({_()~;>*%+..........+@%#%###%*$#%%@+..................................................+@&&#%#*#*$$=>**%%+........+&*=-!){({()!-=*@+..................+&&*,-;!){{)!->*%+...........+@%*,;)]}/}F();->*@+.............+@%##%#%%%%*%&&@+....................@*=-;!)({{';,*%@..........+&###$%%&@@........................&%*>,--;-,$##%@+.......", +".....&%#$*>$**>**=$*$*##%&+..........+&%$>;~(:}[2[}}:1}1(~;>*@........+&$=;(1}}1}('->#@...................+%#=-;~(1}}1!-=*&............+#$,-~|}Q[Q}|(!->*&+..........+&#$-;(|}[[[[}21::1(!->#@..........@#$$$*#*$**$$$#%@................................................@%&#$*$=>,>---->**%@.........@#>;_|}2}21~;>$#@..................@&#*-;~(|}[1_;,$#@...........+%$,-;_}[QQ[:(~;=$#@............+&%#**$*$$$$$$$#%&+..................&#$>;'_(}}1(;-$#&.......+&&%*$$$*$*#%%&......................+&*>,--!;;-,=$#%@.......", +"...+&*$*>=333333,-3,,>=*$$#&+........&%*=-0de44hhh74f44fedc-*&........+&$,cbe7774e~9-*#+..................@*$-;adbe74ea9=*%...........+&%>-0df75kh7eba;,$%@..........+%*>9abe4hhhk7eeeeeba-=#@........+@%*=,,,>=>,-=,>**%+.............................................+@%$*=>,,-3cc90c0c>=*&........+@#=;de4774e_c,*#%@................+&#$=;0dbe47edc3**@+..........@#*-cab4khkkeedc3$$%@..........+&#*$,-,==*=,-==>**%@+...............@%*>3a~be47eb0-=#&+.....+&$$>,,3>,>=**##@....................+%*=,;0ad_dc93=$#&+......", +"...&=,3cc_a_d~dd_dddaa0c3,==*&......&%>,30_e7klVVlllhhhk4|a3=%........@#=9'e[lllhkea93*&+................+%$30d|e4klhedc3*#+..........+&>3ad:klVVVlk4:dc,$%+........+%*,3a14kkllVVlhkkk4edc>*@........+*$,;!aac;cc0aa993>*%..........................................@%#=>39ccadd(bbbee1da9=#+.......&$=99b4klllk7d09=*#&+.............+&**3c_be[kll4e_;3=#+.........+%$-0d:4klVVVk4ed03=>#&........+&*>>;0~aac;c!aa0933==#+...........++&#$>ca(e4kkVked03=*@.....%*,3ca0_daac9-,=*%+.................+&#-0dd1ef4ee(a03=*#+.....", +"..+*!{F^zssssssxsssssz/F]{)!-$+....+$-')]/zxBCBCCCBHCCBysrF~-=+......+%>-']rxBACCAyr]);>#&+.............@%,;)/zyyBACts/{'-=@.........+#$;)/zyACCBCCCxs^F';>#+......@#,;~]/sACCCCCCCHHCAyz^);,%.......+#>-~Frzr^/F/rzz/F{)!-#+.........@@@&&@+.........+@@@@+........@*-;!){F^rssssyyAABBxrF!-&.....+%$-;)]rstBCCAyz/{~;-=#%@+.......@%#=-;;)FrsxAACBxr/{';,*@......+&*,;)FzyACCCCCBAyz/])!-=@......+%*-;)]rzzrFFF^zzrF]{);-#+........+&$=,;;~]rsytBCBts/]);-#....&>-)]rzssssz^FF{~;,$%+..............@#,;)/zstBBHAAxsrF);-*&+...", +"..%-~FrsxBACCBBBBAABAAyz//]{;-%...+#=;)]/rsBCCCCCCGvGHBxs^{~;>%......@#=-!{/zxBCBAyz/]~->*&+............#>-~FryABCBtyz/);-=#+........@#,;]ryABBACCCABxz/]~-$#+.....+*-!{/ztACCCBCBHGGBtsrF)-,#.......%*-;)/ytyzzzzxAysr/F);>%........&%$>==*%%@+..+@%%#*$***&......@$>;!)]/zsABBBBACCGLDGy/{;$+....@*>;~)FrzyAACCBxr/)!--=**&......&*=--;;){/yBCCBBxsrF)!--=%......%>-;~{rsABBAAAACBAsr/]);>#+....@&*,;']rsyysr/zsyyyzr/]);=%......@%#*=-;;~{/stCBBBAsr/{~;-*+..+%-!]ryBABAAAxzr/]~->*%+............@#=-!]rxBCGGLLHCAs/{';,*@...", +"..@$;)F^IzKsKsKzKzKzzKr^F1(~;>&...+&*,;)1F[rKKJJJJwwwJQ2F)~-=*@......@&$>-;){/rzKzQF|';>>#&@..........+@#>,-)FrKzzr^F|);-*#@.........@#=-~F^rKrrrrKzr[]_~;,$%@.....+#,;!)]/IKzPJJJwwwI[]_!-=%@......+&#=-']^r//1FF/rQ/]()!-*#+.....+@&#*$$>$*#&@@@@&%#*****#%+....+&*=-;~{F/QrzKzKsJJwwwO<|!>%....+@%$=-!~{]F^KzrI/F{~;-,>*%&......@%$==--!)]/Izzrr/F|)!;-=*&.....+%*>-;~]/rKrrrIrKzr[F]);->#+....@&$=-;)]/Ir/F|F/rr//F|);-=&.....+&#*$=>-;;']/rzKzr/F])!;-$*+...&=-)F^zKzKzKr/F|);-=*#&@...........@#=,;']^IJwwwwwKK^]~;-*#@...", +"..&$-!(2%&+.......+&#$,!|}Q[[[}[QQQ}:(~;>*%+......&=;'(|}[IKIPPPOOO<}(_;-$%@......@%$$-;|[Q[}:|2[QQ}:(~;,$%@......+%#>----=*#%%&%##*>--->>*@....+%*-;((:}<$&+....@%$=>--;'_|[QIQQ22|1~_;->*%+....@%*,-!_|2QQ[[22QQQ[}:(('-=$&...+&#*,;!_:[Q[:|1|}[[[}1|(!-=#+....&#*>=---;!_|}QIQQ[[1|(_;-=*&..+%*-!1[QIPPP5Q[}1(~-,$$#%@.........+&*>-;(1*@...", +"..&=9abgmqoqnmlhkhh-;abe4h*%@........+#*>,90be45h-0:4hk7747hkh7ebdc-*#@.....+#=;adb44hllmVmoojifbac-$%+.....+&#=>;ab7h744f77<77ebdc3$#@......&*=9caa09=$*$#**$39aaac,=@...+&=,cbefgmjjphkhmoqWWWWqifa3#....@#>9!adb:ee44kk44eebdda;33*@....&*>-caadbbe7kk<774ebbbdcc,$@...+%$=90dbe7,;adbe7hkf:bb44h774eed0-*@...@%$,9;aa_dbb:77=**&+........+%=3cdbgioopigimjoigbda3$&...", +"..#=;_ehnZWZoXVVllllVlXoWUWpgb,+.+%=-a:4kklVVVlllmXnXVVkk74|dc#.....&%=,39d:4klVVVlhk4eda93$#+......@*=,-c_e4kVVVVllk4e(d03>%+.....@#*>3abekllVlllVVVk4:ba93*&....+&*,c~b}7lVVXXXXXXXl7:ba9,*&.....+*=,ca14>33;abebd0,#...%*30b4klVoZZnmllXZZUUTUqp7b0$+..+#>9de4k7kkkkk7k7kkkkk74e(a9*...@#>9a|44kkkk9ad|e47k7kkkllllIVlkkk4410>+.+&=3aeklmnZWoXVkk44bd0c3=$%+.......&*,0de4hmZomlhlmoZni4ed0>%...", +"..*;{^stDEELDCCCCBCCCCHLEEEGs^!@+&=;{/sBCCCCCBAtxABCHBHCCAAsr{=+..+%=-;~]FzsABBCCBCCAysz^F);-%.....&=-!){/ryABCCCCCCCxsz/F)!,&....+#-;~{/zxtCCCCBCCBCBysr^{~-*+...%>;!{/rsxBCCCGBGCGHAxsrF{!-$+...@*-;~]/zxACBCBBCCCCtysz^]~;#.....#-;)^zsszrF{)~~))]FrzssrF'>+..*-)FzACCHGLEGHCHHDEEEEEDGsrF-#..%-!]rxBCBCCAAysyyBBBACAAAsr],+.+$-!]zyBABCCAAyxxABBCCBCBAyr],+.+%-~FzxBBCCCCAyssxCCCCBCAtsr];+++$;'FztBCBCCAxsssxACCACCAtsr{=+.+$;{/zxBBBBBCCABABCCCCCCBAAs^;@.+*;']ztCHGLEvHCCBtysr/F{)!;=&......*-']^zxBvLDHBACHDLDBxzr]!>+..", +".+$-)/zAGGLGGCCABBABBABGGLDGA^'%+%,')^yBCBCCBCByttBCCCCCCCBxr{-+..@*,-~]/rsACCCCCCCCCCAysr]);$+...@*-!)F/rsACCBHHCCCBCyysrF~;=+...%,-!)FrzyCBCCHHBCCCCAysz/{!>%..@#-;)]rzsACCBCCCCCCBBAysr]~;>&..+#=;~{FrsABACCCCCCBCCAyszF);$+...+$-~]ryBBxzr/F]{]//zsABAs/)=+..*;)FstBGCvGvHCCBCGLLELELtxrF~#..#;~FsBCCBCCCAsssytCCCCCCByz{-@.&$;)FsACCCCCBAyssyACCCCCBBxz{-+.+*;)/sCCCCCBBysssyABBCHCCBxr{;%.+>;{/sCCCCCCByszsyBCCCBCCByr{-..+=']rsBCBCCCCBBAAtBCCBCCCCByz~@.+*-~]ztBCvLLLGCBCCAxszr/F)!-$@.....%-!]rstAGGvCBBBCLLGAysrF'=+..", +"..#,;~:[IJJJJIrI^^^^[QQPJJ6KQF-@.@*-'(QJwwwwJJrQ//QrPJwwwJK[{;*...+&=-!(F[IKJJKKKzKKzKJKIQ|~-=+...@#>;~]}[QKJJJKJKKKKKPPK[:!-*@...&=,;~|F^QrKKKKPKKKKJJPKQ|~;=@..@#*-!(F[/rKKKKKKKKKKJJKI[|!-=@..@**,;)]}^rKzKKKKKKKKJJPI[('-#.....%=-~2IJPKQ2F|))(]}QIJPJQ1;*...&=;)[PJwwJPPIIIIKPJwJOOJPQ})-%+.&,-)[KwwwJKr[FFFF[IKJwwJK^]~=+.@%,;([JwwJJKr/FFFF^IKJwwJK/1!=...%,;(QKwwJJKr^F]F2^IJOwwJK[]!=@..%-;1QJwwJKKr[F1F2/IJwwwJI[{;*..+#-']/IJwwwJJzQ//F^IKJwwwJKQ(-+..&=-;{/QKJwNNwwwJJJJIQ/}]);-*+....+&=-!)}[QPJPIQQQPwwOK/}{~-#...", +".+%*-'_:QKPPQQ[}:1||11}Q5PIQ[1;@+@#=-(<6OwSSY8<[}}[[<6OSYO<}(c#...&$>;_1}Q5JOPPQQQQ-'|[QQIIIIIQQQIIK8MMO<:'-*@..@#>-~|}QQIIQQQ[Q[QQP8OYO<1!-$+...+&=3(78OOOI<}:_((2[QKOOO5:c*+..&=c([6wSOP<[[[}[[IIIPPP3_46OwOP5[:(_(11[KOYYO52(;*+.+%>,d[OYwOP<[1(((|}[8JSYO<}(;=@..%=;(<6OwOP<2|(_(|2Q5YYYO<}(;*+..&>;_1<8YNYO%+..", +"..&=90bfimpXik77eb:bbbfgillhhfc@++#>cbgqWWUUUji7ff44hpWUUqieb;%...&=-cbf4hmoqomk7khhljqWWjgbc3&..+&*-ab4khmoZomh4khhmpZWWjgbc>%..+*>3ae4;ae7hlhllkhk7khhmjWWqpfdc>@..@*,;df70bgjWUqpg477774hhlmmlik74b9%+.&=3dgqWWZp57ebadd1fiqWWqm7bc*+.+&>9dgjWWqnk7ebad1b7iqUUqi7bc*...%>cbgqWZqmh7ed0ddbgmqWWqieb;=@.+%>cbiqWWomk4bd_abegpqWUqiedc%...&=9abgjqUqqik4:bb47hpqqWqiea>...&*>-abfgiqqUWqqqqqqmllh7fbc3&.....@*,;def4hihhghijqUjifed0-#...", +"..#,cdehmoZoopnlk7444e4hmVVVhk0%.@*,ceiZWUUTUZmhkkkhlnWUUqpkba*+..#,0(4hVVnZZomlhkllXoWWUoifd9*..@*3cb4hlXXZZnmhkklVVoWUWoiedc#..&*3017lVlVVVlhkhhlVVoWUWjhea3#..@=3a:7VVVVVVVhkhklVmqWWWjhba3#++&=3a:klXVVVVVlhkhlVXoUUWjgba3%...+&*9cegmnmnnmlhhhmXonmmigbc#+..%9afioWUWpikkllVlllVmmVVVVkea*..&,cbioWWZnVke1bdbf7pqUUZj4ea>+.@$>cbiZZWZnVk4|bdbfkmZWUqmhba$+..%3cbioWWZnV+.%;)FzAHGGCCCCtyttBBCGvLvvAs^)=++#;)/ztHHGBCCAAxxxtACGDLvGts/)*..#;)/stHHHCCCAAAxytBHvDEDGAz/'*...+#>;_2rsyGDEvGBAHvDLGAys[{'=+.+=)/stLLELGAtACCCCBCBCCBCBCBs/,++*!{rtvLLDGCts^/^^zsHEEEEvxz]-@.@>'{zyvLLvvCtz^//rzxHvEEDGyr{-+..*!{KtGvLGHCtzr/FrsyGDEEEGyz];%.+=!{rtvDvGHBxs^/^rsyHvEEDHxr]-...%>;(^sACHGCHCAysstACCHHHHxr(>...%,;)/zytCBHHAyyyyAAACBCCCBsF-....+*-~]rytBBGvvDLEEEELHtxz/);&..", +".+$;]ryHLLEEEELGCBtxszsxABCCBy]#+&>']ryCCCCHGCBCCCABAHLLELAsr{=+.+>)/zAHLvvHCtszzzssAABHGHCAz{-+.%;)/sAGLvGHCxyzzzssxACHHHBxz]>++%;{/sAGDLGCBtsszssxxCCHHCAyr{=+.&;{/sHvLLGCAAszzzsyABBHHCAyr(>++*;]rsHGLLCCAAsszzsxAAHHHCtyr)=...+#=;~]^zsAGLLGHCHDLLvAsr/{;$..+>_/KAvLLvHyABACACBABCCCCCCAs]-@+=!FryCCCCCAyz/]F^rstGELLHyr]-@.@=;{rxCBCCCAsz/{{FrstGLLLtsr],+.+$;]ryCCCCBAyzF]]FrstLLEGHyr];%+@$!]zACHCCCByr/FF/KsALLLLtsr{=+..&=;)FzyCBCBCCBtyxyCCBCCCAs^)=+.+#,')/zsABCBAyssszsyBACCCCAy^-@...+#-)]zsACCCHLLLEuEEGHByz/{;*..", +"..%,!1[IONNNNNwJKQ/FF{]/^QrIr[~%+@*-!]QKKJsPKKKKKKr[QPwNwOP}(;*...*-(}PwNwwI[/:()(]{2QKKKKIr}_=+.@,;(}PwwwJI[F]()({]2QKKKKKQF_=..@>'1[6wNwJK^F]{){)F}QKKKKIQF~*++&-!|Q6wwwJI^F]()({]2QKKKKI^]'*..%-!:QJwNwJI^F])))(][IKKKKI/1'*....@%=,;~1}+++*=;([KKJKI^}|~;;'(2;1^IKJrI/:{';;'(25wNwOP}|!>@..%-;|[KKKKQ/}('!!~{25wwwOK2(;*....&$-;(F/^IPJJKI/^IJJJKI^2]~-#...@*,;)F2^IKKr[2]:F:/QrIKKzI^{=+...+%*-!]F/rKKJwwwwNwNOPI/2{~-&..", +".+%=;(1<6SSNNYJ5[221__(|}[[[Q[!&+@#=c_}QQIIIIKIPP5Q[[5OSS8<|_-%..+%-0:5YSS6[:('!';;_(2[QIIQ[2(>+.@=9_15OwY6[1(!;;;'~(}[QIQQ[}~>+.&=;_ePRNS6[1(_!;;;~(}[QIIQ[1~=+.&=;(}6RNR8[:__'';!_12QQIIQ[1~$+.&,c(46SNY5[1(~;;;;_(}QQIQQ[:;*.....@#=,3!125OwYOO6OY8<1(~;-*&...%-01c_}QIIQ[}(~---;_1QOSSO<1_3*@..&,9(}QIQQ[1(~---;~{+.@$cdfiqWqpgba9-9-90ae7hkhh7fd=+.&=cdfpqWqpfb_c-3-30de7khkhkfa=..&,cbfjqWqifba;3-33cde7khhhkf0$+.@,cbgpqWqifd0;9--3cde47hkk7ea*.....@#*,9cbfhioqWUqjpigba0->*&..+%3abgjWWqi7f7hlh74ff74hkhh4e0*.+&=3afkhlk7edac,--9dgjWWqieac#...%=9afkhhh7eda3-,9;bgjWWqgea3%...%=cdfhhhh4:b03,>9abgqqWjgbac*@.+&,0d4hhhh4edc3,,-0bijWWjiba3#....@%#=3aab7ioqmi4hioqpgeda9,#+...@#*9abe4hhhkfbbbdbb47khhhkfd>.....%*3cdb47hhlhhg7gimpjjjpgd3%..", +"..#,cbflnWWWoXlh4ee1bbb4kklllka&.@*30|7lXVVVVVnZonmhhXZUWZifdc%...$0d4pZWWjhbac33,39deklllllkb,+.&,cbgpWWWphba99>,-c(ekkllllkb,+.&,ab7pWWZj7ba99>-99beklllVl7b>+.%30egnWWWp7b~93-,3cb4kkllVh7d>+.#9aegoWUZpfdc9,3,9adekklVll4d>+....&*>30d14hmnWTUUoXVk4bac3*&...$9demoUUZphkklVll[4kklllllh40*+.&=cd4lVVVh4e0;339abhoZWqm7dc*+.+%,;b4lVVVk41ac39cabioWWqmfdc*...%3cb4VVllk71ac3-caeiZUWZm7d9=+..#3cb7VVllh4|dc-39abiZWWoifd3%....+@*>-0db4poZnVllmoZohedc9=#....@*=;a14klVVlk42ee44klllVll7b,+...+&=-ab4kkVVVl744..@-{rsHLELvyr]);;;~)/ztAABBCtr'@.%;]/yHDEEGyKF)!;;'{^stCCCBBx^;&+*!FIxvEEEGyrF_';;~{^stCCCCBy/;@+*_FKxDEEvtJ^]_!;;~]^sACBBCAy/;@+*~FzAvELLHK^(~;;;~]rstCCCCAs/-@...+*-;)FrsxBHGEEEEGGHAsr/{~-*...>)/zAEEEEGCCBCCCAyxxACCCCCts]-++*;{FsBBCCAyz/(~)(/rxDEEvtJ^(-+.@$;{^sBCBCCyzF{'){/zyvEEvHs^{=+..*;{rxBCCCAtz/(~~)FzADEEDts^)-&.+$!]rxBCCBByz/(~~)2zAEEEvtK^)=....+@#=-']^ztDvGGGGGDDHxIF);>#....#-;)/zxACHHHBtyyyxAAHHBCCAsF-+...+*3~FrytACCCAxssxAHHLELvtKF-+.", +".+%-~{rsACCCCBtyzrr^^^zsBCACBx]#@&>!]ryCCCCCBCBCBHCBCHLLLLAs^(=+.+-F^JtLLLvxz/)~;'~]^sACCCBBA^;&+*!]rsGLELvxz/)~;'~{/sAACBBCyr;@.*;FrsGLLuGyrF)!;')]rsBACCABx/;@.*;FryvLELHsrF)!;'){rsBBBCBBx/-+.='FKxGLLLAsrF)!;!)]ryBBACCBx/;&..+%=-~]/zxBGvvGvHvGvvCxsr/);*+.+=)}stLuELGCACCBABxyABBBCCCBsF,++*;)^sBCCCBxz^])){/KAGLLLBs/{=+.@=;{/xBCCCByz/])){/KtvLLLts/(>+.+$!{ryBCCCBxz/])){/KtLLLuAs^{-&++>!]ryCCCCtxrF{)){/KAvLLLAzF)>+.....@*-')]KyBGGLELLGHts/{'->%+..+&,;)/zyBHGLGCBCBBCCGGLvGCyzF-+...+*;~]zsBBBCCAysssxxCGLEDHsF-@.", +"..@=-;(2IKJKKr^FF()))(]}rIrKr['%+@*-!]/KKJKKIrIKKPKPJwwNww52(;#..+=;([8SNwO<11~;--;!)/rzzKzI^],++&,'([6wNw8Q})';-;;'{/rKzzKr/(>+.&>!1[6wNw6Q1(';--;']/rKzKzr/)=+.&,!1<6wNY8Q|);;--;~]/IzKzKr/(>++%-_:;)/IKzK^/](~;;!(2!{/KKKK^/|)';;!(}PONwOI2_,%......+@*=-'(}IPOwNNSJ<[]_;=*%@...+@*,;):/QJwwwJJzzKsJwwwwJI[{-*.....&>-~]FQrIKr^F:F:F[KOwww<2_=..", +"..@*=-~|[IIIQ[2:__~;!_1}[QQ[Q};@.@#=c(2QIP5Q[[[[[QIIJOwNS6<:_-%...*;_:5RSR8[:(~;;-;~(}[QIIQ[2~>+.@=c_15YSM821~!;;;;~(}QQIQQ[1'$+.&>cd}8RSY6[1(!;-;;_(}QQIQQ[:!*+.&=c_28YSR6[|(';-;;_1}QIIQQ[:'$+.&-;(48RRY5}1~!;-;!_|}QIIIQ[|!*+...@#>-'(|[#...#-~1-~:[IQQ[}(('!;'_:[OSSO<{~-%+..&>;_}QI0bgpqWqigbbaaa0ad:7hilk77d9%...+&*,;ab7gijqoihhimqjpi7fba>#...%-cbgjqWqomlh774eee4hillkhfd3&..&=3abkhhh74bbdaaabbijWUqiba3%...&>9a:7hhh7ebbaaaadfgjWWqiba3%...@=3abkhhk7eeddaaabfgoWWjiba-%...%=3aekhhk7f:daaaabeiqWWjgba-&........@*=-caegpUUUjieba9,$%&+....@#=;abfgpqWWomllhlmpqqopfba,%....@*=3cdee7hh<7ebbbefgjZWqgfc$..", +".+%>30d4mnonmlk74ee:e47hmnXmhe9@.+$3abhnnnplhk774klVXoUTUqifdc*...$9abinoZnVk74e:1e47lpXoml7ea*+.@=9afioZonlk44e1e24khnnnmh7ba$..&=3dfioZoXVk7e:e1e47lpnnmh7ba*+.@=9afmnooXlk4ee:eeekinonmh4bc*..%,3dfmoqonlk4e:1eef7lXnnmh4dc#...+*=3afimXnonnlkkhmnonmmmgbc*+..&,0bgpZZZoooml9_ehIVlll74eee:f7mqWWqifc3#...&=c_ekVVllhkeee1efhmqWWoifa3%........@$>9a(e7pWUUqi7:d03**%@....%=9ab4klpWWZonXVVVVXmpigfac,&...&#=30(4[lllVl+.", +".+*;~]^svvELGHBtyysxxABvDEDHxz~%@&,!]zxDvvDHCAxyxxBCGDEEEEts[)=..+=!:rxHCHHCCAtyyxxAAHDDLvts/_>+.&-~]rxHvHHCCAAyxyxAAGvDvvAs/_>+.#-~|IxHCHBHCBAxyxyAAvDLvvtz/)=+.%-'FzyBGCHBCAyxxxttBGvvDGtz^)*..&9~FztHHHBCCAyyxxAtHGDLDHxKF'*..+%-;{ryvvvvGHtysssxCHGvvGGK],+.+*c)/sAHGGGLvvHAAAAAHGvDDtyrF;$..%,~]rxACCCCAAyssxyAGDEEGtK/~=+..%-'FzyACCBCAyxsxxtAGEEEEtIF~>...%-)FzyCCCBCAtysyyAAHDEEvyI{;$+.+#-)/zyCCCCBAxsssytBGDEEvyI{'$.......@*-!)FrsAGuEELAxz^]~;,*%...+>!{/syAHGEELGBCCBCCByssQF)!,%...%>;~FzxtCCCBCCBBBCCAvDEEvGKF-+.", +"..*;)FzxvLELLCCCBBABBCHvLvvGs^~&+%-']IADLLGvCByyyABCHvEEEuAsF)>+..$;(/stCBCCCCCABBCCCGvLGGyz/)=+.@>!{rsABCBCCCABBACCHGvLvGxzF~$++@>;)/ztCCCCCCABBBBCHvvLvHyzF'*..@>'{ryBCBCCCCABBCCCCHvDvHsz];*+.&,;]rtACCCCCCCBBCCCCGLvGtsr]!*...+$!{/JGDvHAxszrrzsytHGvDAK{=+..*;)^sBCBCvDLGCCCCCCGLLLLAs/{;%+.@>;]^sAABBCCCABABCCHGGLDyr]'>+.+&>;{rsBBBCCCBAxACCCGvLLDyr{!$+..%,!]ryAAACCCBBABACCHvLLvxr);*+..%-']rxAACBCCCABABCBGvLLGxr{;*.......%>;~]/stHLLEELHAsrF);-,#@..+=;]/sBCCvLLLHCCCBBAxsrF{)!-$+...#-;)FzyCCBCCCCCCBBCCvLELDts/-@.", +".+&>;(:+.@=-~15wNNNwJKIFF/^IPwwNNY52(;%...&$-~1F[^QKKKJPJPJJJJP6P<}(!-#...@$-~]2^QrKKKJJPJPJJOP6P<2(!>&..+&*-'(F[^QrKKKJJJPJJ868P<:(;>&...#>;)]2^QrKKJJJKJPJJPP65Q:_;>@..+#=;~F}^QrKKKJPJKJJJJJ6PQ:_;,&...+&=;_}[5<<[2{_'~~)]}[<5<4(;*...&>;)2rKKJJJJJJJJJJJJO685[:);=+...&=-)]F^QKJJJJPJ6JJJJJJP[_;-&...@*=-)]/^IKJJJJPPJJJOOJ6P[(;>&...+%=-)]/^IKJJJJPJJJJJJJJK[_;=%...+*=;)]/^KJJJJJPJJJOJJJ8K}_->&.......#>-;~:QIJwNwNNOPQ}(~;,*@....*-~]QPJJwNwwJKKrQ/2{~~;-==*@....&*-!([IJJwwwJJKrKKJJwNNwO5:_$..", +"..%=;(2-!(|:}QKPOOYYYYY65Q[2(;-*@...@*=-;(_:2QKPOOOYYOO6<[}|_;-#+..+@$=-!((1}QKPOYYYYYO5Q[}(~9-#+...@*>-'((12QIKOwOMYY86<[2(_;>#+....@#>3a(1:((';;->--c'(111_c-#+..&*,'([QIIPK6OYSYSYYO8<[2|~-=%+...&*=-'(([5OwYYYRSRYO6<<[|~-=&....&*,-'_|}POYYRMRYYY8P<<41'-#+...+%*,-'(:[6OwYYYRRYYO6<<4|;,>@...+&*=;~(|[6wYSRRRRRROP<<}('-#+......+&,;~(158MRNNNNMY5}|_;-*@...+%-_:0b7ioWUUUUUUWWWUTUqpg7ed0,%..+*-cbiqWUUUWjgeb1e4hmqWWjgba-%....&$,90_(e7hmqWUUUWqmhfbd09*%+....&*,90_be4hmZUUUWWWph4fd09=%+....%*,-0a1e7lpZWUUUWqigbbdc3=%....+%*,9a_bekhpWWUUUUqigfba0-$&....+%>=9cdbehinqUUUUWqmgffdc3*%.....@%*,;00aa;3,>>$==,30caa03=%...&*,cbf7hhmmnoWUUWWqoi47bd9-#@....@#>>9adfioWWWUUUUWjmh4fd0,$&....@#>,;abfpqWWUUUWWWjihffac=#.....&*=-9abepqUUUUUUWWjih4fac=$&....&*$-9abgpqWUUUUUUqjik7fa3=%.......+#,cabfiqWTTUUTTWjgeda-=@...@#,0fiqWUUUWjmk4eba;3==$#&@......@*-;bijWUUUWqph7hpqWUWWWjgb3%..", +"..%,0ekmoWWUUTUUTUUUUTWph4edc>%..@#30fiZWWUUWoifebf7hnZWWqibc>%....+%=-0adekVpZUUUUWZph4edc->&....+&$=-0adehVnZWUUUWZjh7eb09>#.....@*=39abfhVnZUUUUWZmh4bdc,=&....+&*,90ab4kXnZUUUUUZm7f1d9,=%.....@*,-0d14kVoZWUUUWZmh4edc,=&....+@#$,9c00c9>=*$$$$,,900093*&...&>>017llVXnXZWUUUWWomh4edc,$@....+%*--cdfpZWWUWUUUWZmhkebc,*&....@&*,30d7pZWUUUUUUWomkked9=%+....@%$=9cdgmWWUUUWUUUomh4ed9>#+....@#*39cb7oZUUUUUUUWoVh7ed9=&.......@*3cde7pZWUUUUUUUoiebac=&...@>9aepZWUUUWZVlk4ba3>$$%@........@*,cbiZWUWUWZphhlpWUWUUWqgd3#..", +".+*;{rsAGLLvLLEELELELEvtxKrF)3*.+&>;(/yDvELEvvyz^^rsxHvLDGs[);*....+%=-!)]rsAHDDELEDDtsI/F_;-#.....@=-;!)FrstHGLELLLvHxzr/);>#.....+*-;!)FrxAHGLDEELGtJr/]_;>%....+&$-;~)FzsBHDLEDLvvtsr/{~;$&.....@$-!)F/zyAGGLLELDDAsz^F~->&....+&#=-;''';-->=***>,3;!!~;;>%...%>;)^sBCCCCHHLLLLLLHtsz/]'-=@....@%$-'_]rxDLLvLLLELHAtszF~-$&....@%$3;(FKALLLEELLLLGBxsrF~;*+....@#>-')FzADLELLLLDDGBAsz]~-$+....@#>;')FKADLLvLLEELGAysr]',$.......&=;~F^sAvLLELLELvHxz/]);%...@,;{rxDELLLDGtAszF);-$#@+.......+%>;_/sGvLLLDGHttAADDLLLDHK:-#..", +".+*;~FrsxBABBBBAHABBBtwsrF])',&..+*;):rsAttAtsz/]]F/rsytxs^{',&.....+%=;!)FrzsyAAAAtxsr/{)!-=&+....+&$-;!)]/zsABABtAysr/])',$@......+#>;;)F^zsAABBAAsz^F{';,%+......+#=;')FrzsABAAtAyz/F{~;,%+.....+@$-'){FrzsABBAtAysrF{)',%+......+&#--;;--$%%&&##=>--;;->#+...@=;'/zytAtytBCBBBAtyz/F{~;,#+.....+#=;!(FKyABBBBBtAysr/]);,#+.....+#>;;(/zyABAHBBBAxsr/F)!,%+.....+%-;!{2zxAABBBBBxyzr/])-*&......@%>;'{^zAABBBBABxyzr/]);,&+......@#,;)]/KytBAABAtysr/{);>%...@$;)1ryAABAtssr/])!->#+..........@$-'{rsAABBAyszzzsxAAAyxz/)-&..", +"..@#=-;~)({|{|11:1|((()a!;-,*&+...@*,-!_(((((_!;---;;~_(_~;-=#+.......@*=-;'~)((1(((_~;;->=&@........@&*=--;'~)((((((_!;-,=%+........+@*=,-;'_((((((_!;--=*&+.........+%=,-;~)(({(((__!-->*%.........+%*,--'~_((((((_~!--=*&.........++&&&%&@@++..+.@&#$***%@....+&>-']FFFF{){{{{{(((~;;->$&+........@#$,-!_({({({((__';--$&+.......+@#=,;!_(((11{_()_!--=#%+........@*=-;'()(({{{1))~!;-,*@........+&*=,;~(((1111((_~;;-=%@........+&#*=--!)({|{(1(((!;-,*%+...+@#,;!_((((((~!;--=#&+...........+&*=-;_()(|(((_~__)((((~0-=%...", +"...@#*=--------;;;-99---,**%&+....+%*=>,--,3--,==***=,,-,>**&+.........+%#*=>--;;3---,=**%@++.........+@%*>>>------,,,,**#&............+#*=>,-,-----,>**#%++...........@%#$>>,---9,-->>**&+............@%*>>,------>-,>=*#&+.........................++++++.......@%=-!___!;--->---,--=**#&@..........+&#>>,,--------,>>*%&@..........@@*$>>--------,,>=*%+..........+@%**,-----;----=>$$#&..........+@&%>,>----;9---==**%&...........+&#*==-3-----3-,,=*#%......+&*$>=-----,->**#&&..............+%**>>---------,3----,==*#@...", +"....+%#*$*>*>,>>,,==>$$***#%@.......&%*$$=$**$$######***$$%&@...........+&%***>=>>=$>$*&&@+.............@&#**$>>$>==$$$*&@+.............@&%##$*>>>$>*$*&@+..............@&#**$$**>*$=**&@+.............+&&%**$>$>$>$***%&@.........................................@*=39333,>=,=*$==$$*&&&+............+@&#$*>*=$$$$*=**#&+.............&*$$$>**$>>>**$*%@..............&%*$$====,>*>***%&+............@@%*$$=>>=-=*>**#%@.............@%#$***,*,$$*$$*#&@+........@%*$$>$$>*=$*&@.................@%%#***$>$=$>>**=$*$=*$%@....", +"......@&*$*$***********%%@++.........@@%*#****%&@&&@&%&%%#&+..............+@#*##$***%*&+.................+++&##%**##**%++..................+%#$****%*#@+.................+@&%$$$$*$*##+..................+@@%%#*****$*%%@..........................................+@**>>,=$>$$***##**%&+................@&#***$=****#&@++..............+&##*****###$*%&@@..............+@%**##*$#**##&@@...............+&%#*#***%$$%$#@@+.............++@%#$$**$=*$*%&@@+..........+&%*%**%*#%@+...................+@&#*$*****#*$*****##&@.....", +".......+@%&%%%%**##%%&@................+&&%%%&@+..+..+.+@&@.................&%%&%%%%&@+...................+.+@%%&%%&&%&.....................+@#####%%@+...................+@@%######%%+.....................&&%%&%&&%%&&+...........................................+%*$$*%%%%**#%%%%&@+..................+&%#%%*%%#&@+...................@%&#**%%%&%%&+.................@&&%&%&%%%%&&+...................+&%%%##&&#&&&+................++&&&&%&#%*##@...............+@#%%#%&#&+......................+&&&%&%&%&&%%&%&#%%&+.....", +"..............+.+........................+...................................+.+................................++++++......................................................+@&&&&&&@...........................+.+...................................................++++++.++++++..+......................++++.++.........................+.++++..........................++..............................++.......+.......................+.+.+.......................+..+............................+.+.++++..+.+..........", +".............................................................................................................................................................................++++++++........................................................................................................................................................................................................................+..................................................................................................................", +"...........................................................................................................................................................................+@@@@&&@&+.......................................................................................................................................................................................................................+++.................................................................................................................", +".........+@@@@@++.................................+@+@+........................+@++........................................................................................+@%####%%&+...................................................................................+++@+@+..................................+@@@@++....................+@@@++++....................................................++@@+++.................................+@++@++.........................+@+............................................", +"........+&%##**#&@+...........................+@&%%**%%@...................+@%%*#*#&+.....................................................................................+%$*,,>,=>$%@.................................................................................@%##**#*%+..............................+&####%##&+.................+@***$*#&@+.................................................@&*#*$#%@+..............................@&#****%%+.................+@&%#%**%@...........................................", +"........&=--;;;;-$#&+.......................+@#=--;;;;,$&+...............+%=--;!;;;>$%@...................+&#******%%&@..................................................+#-;'~{){)!;=#+..............................................................................+%=--;;;;--*&+..........................@%*=-;;';--$#@..............+&==-';';;>##@..............................................+&=>-;;;;-$#@...........................@&*>-;;;;--*@..............+@#=-;;;;;-=%+.........................................", +".......%$-;!)]])!-,=#@....................+&#*-;;~)]]);>*@..............+%>>;')]]]~;,*&................+&&#$$-------->*%+.................+@@&&&@+..++...................%=;'{F/^//]~-$#+.............................................................................@*,-!~{])!;-=&.........................+&*=-;)]])~;-=#@.............@*,;~)]]]~;-*#&................+@&@@@@@+@@@.................&$>;!)]{);-=$+.........................&*$--~)]{~!->$&............+%==-;){]{)'-$%+.................+@@@@@@@&&@+...........", +"......+&#=-;!)(~;,=$%@+...................+&#*=--;~)~;-*%@..............@%$=-!){_)'->*&................@%%#>=->>,,,,->=#&+..............@@@&&&&&@@@@@@+.................+%*-;)(F[F});,*%+............................................................................+&*>-;!)();,=$%+........................@&#*,-'))';-=$#@............+&*$-;')_)'-=$%@..............@@&@&&&&@@@@@&++..............+%$=-;~_)!-=**@.........................@%$>-;)))';-**&+...........+&*=,-~)))!;>*%@................++&&&&&&&&&&@+..........", +".....++%*$-;_((_;->*#&+..................+%#*$>;!~~((',$#+.............+&$=-;~~(1(!-=*%+..............+@#*>,-;;;-;;-;-,=*#@............+@&%#%%%&&&%&&&@@................@%*,;_1}}[1(;->*&............................................................................+&#$>;'((_;-,=#&+......................+&#$>-;_((_;->*&+............@%*=-;((1(~;-**&+............++&%%#&%&&&&&%&%@@+............+%*>-!(11~;->*#+.......................+&#*=-'~|(~-,>#%+..........+@%$>-;_(|(~;,$#%+..............+@&&##%&%%%%%%&@+........", +"......@%*>-cdbbda;9,$#@+................+@%$>-cadbbb_a-*#@............@%#$,;adbeebdc9=*@.............+&%$=,9cdddadaaa0;3=$%+..........+@%$$$=**$$#$$##%@+...............@#>3cdb4774ba3>$#+...........................................................................@%*>,;0beba!93=%&+.....................@#*=-;abbbd!3>*&.............&#>,;abeebd0;>*#&...........+%%#$***$###$#*###%@+...........@%*>-cbbeda;9,*%.......................@*>>-cabbb_c-*#%+..........+%*=-;0bbebbc9>*#&.............+&%#$$$$*####*$$#&+.......", +"......@#>=cae[k4ee(ac=$&................+%=,3'be4444ea9>*&............@%*-ade47kkke(a9=*&............&*,30~bf7ggkgg74ebac=$#+.......+@%=,3ccc93=,33cc3=#@...............@*,cd47kkk74bac=*@...........................................................................@%$>90b4444e{ac3*%@...................&*=30db14k4ea93$&............&*>3ab47kkk4eda3=*&.........+%*>-cc093,,,3cc-3=*$&+..........+#$,ca:474eeda9$%....................+%*>9aa|e4k4ba9>*&@.........+%*>9d:27kkk41d93$%@...........+%*,39c'c3-,,-c99=#@+......", +"......@*-;)FzxCAAAsz]~-=%+............+%*>;~]rsACBAsrF'-,#+..........@$-;)/sACBABAAsr]!-=@..........&=-~]/zstGvvvvGHAAsr]~;=%......@#=-;){/^rF{)){F//{;-$@............+&$-']ryBCCAyszr]!>#+............+&&&&&&%@@@&&&&&%@+.................+@&%%%%&%&&&%@+...........@#-;~{^xABAxyz/);-#%+...............+&=-~{/zyyAAAs/);-#..........+%=-']rstBBCCAAs^);-$@......+@#-;~{F/r/])))]/^F)~;-$%+.........@=-;)]zxCAAxsr]!-#.................+&*,;~]rzyABByr{~->*@........+%>;!]rxBCCBBAsr]~-,#+.........&*-;)]F/^^{)))]//F~-$&+.....", +"......&*,;~FzyACBBts/);-*@............&#=-!]/zACBBtsr]~;=#+.........@#>-']rxBBCCCCAxrF~;=#+........+%=;)/zsAHvDLDLDvCCyz/);,%+.....@*,;)]/rzzrF]]FrzrF~-=%+...........@#>-)/zxBABAyyzrF~-$&.........+&##$$>>>$*$$$*>$$$**%+............+@###*====>,=>>$**&@@.........+#>-!)rsBACBAyr]~-,*#@..............&*-;)FzxyBABtz/);-%+.........+#=;)]zyBCBCBCCxz]~;,%+.....+*>-'{F/zzr/F]F/zz^])!;-$&.........&#-;~FzyBCAByzF)-$%................@*>-!)/zytBBAsr{'-$&+........&=-;)/sBCCCBCAyz/);,#@........+#,-~)F^zsz^F]F/rz/);>*+.....", +"......&%$=-!{/^rrr//(;,=*@...........+&#=,-~{F/rKr^F(~->$@.........+&#=,;~]/rKKrIrr/1);>#&+.........&*-!):[QP8JOOOJJKI/])!,*@......+%*-;'(]F}F{_){{F]);>*@+...........@#=,;)F/r^r/^FF|~->%@........++&#*****=$*##$***=$***%@+..........&&%**>>>>=>>=*=$*$#@@.........+&*>-;)F^rrrr[]);-=#%&+............@#*,-!)F/rrrQ/]';=*&..........@#*,;)F/rIKzIrr/F);-$%+.....+#*,-!){F}F|_({{FF{)';,>#%+........+&$>-~]F^rrr/F~;>*&................&$=,;~{F/rr^/F~->#&.........@%*=-!(/QrKrIrI/F);-$#+.........@*=-;')]FF]_((]F1);=*&+.....", +"......@%#>,;_(2}[Q[}(;->#&+.........+@%#$,;~_:2[[21_~-,*%&.........+&$>-;_(2[[[[[[[}:_-=$&+.........&$=;(:1[[<<*@.......&*=;'(_:1:|(|(:1_!-*#%+...........+%$=;~1}2[[Q[}(~;-*#@........@%#*$,>,,>,,===>,,>,,>=**%+.......+@&#*$>=,,--,-,->,>=*$&+.........@#=-;_(:2[Q[[1~!;>*#&+...........&**=-;~(}[[[2}|~;,>*&.........+&#>,;_}[[[[[[[[2|~!-,*&.....+%$,-;~_|}::(((|111(~;-=*%+........+@#=-'~112[[}|_-,$&...............+&*>-;~|}[[[:|~;,*#+.........+&$,;'(1[[[[[[21|~->*@..........@*>-;_(|::|1((_|(_->#&......", +"......&#*=-cdb:e7hk4ba;,*%@..........@$*,-0d:f4474eda9,$#@.........+&*-;0dbe474f4e74eb0,>#@........+&$,;def4747khhhk4fbd;>$#@......@#=-cabbeeeeee1eeba;>$%+..........+&*>-cab44*#+.....+&##$>,3cccc;c00;09cc3=*%@........&%$$-;ad1f4kk7fbac9=>*&+........+&%*>,;abb47=%+.......+&$*,90bf74444477f:bac9=&.....@%$,90abbeebbbb:febbaa9>*%@........+&$=-0d1ee774edc,*%...............@%*-cabe4<74bd0->%&+.........@%$,;0be4744ff4eba3>*&.........+@#=-cabeefeeeebeda;=*@......", +"....+&%=>c'dbe43cadb(bbddddd(b(bbd(ac9=#.....&**,39c_dbb:b1b:b(bbda03>#+......&*$=3cd1eekhlVhk4:dac3>$%.......@#*,3;a_b}kklllh4e1dac3=%.......@*=,3a(4hIlhkkk*@........@%*,ca|e4#+.............&*>3;db2klllkedc3=$&..........@%=3~be7klhkkkk4e(03*&.........+%*>9abe4kkk77744ed9,$&......", +"....&=,;)]^zsxtBCCHAAsr]);-$&......@*-~{/rsxACACCBtxr/{;--%+......+*-!{rzyACCCABCCCAts/{!-$&......@#,;{^sACCBCCCCHGBCAsrF~;,*+....%,;)FrssAACHBBCCCAxz/);-#+.......+%-;!{/rstCCCHCBAxz/{~->%+....+$-!{/zsssssszszssssssssz^]'>@...%>-')]F/zzssyyxsxyssssz/{!,&.....&$-;~]/rsyyCCHBCBAyzr/]);-%.....@*-;~{F^zsAACHCCAAxsz^]);-%.....@*-;)]/zyACACABHCBBAszrF);%...+*;']/zsyBBBABABBCBxyzz/]'-*+......@*,;~{^zytACBCxzF);=&............&$;'{FrsxtCBAAz/);-$@..........#,;)FrstBCABBBBys/{;-%.........&*-!)FrstCCCCBByxz^);-%......", +"...@%=-)]rsyBGvGLGLHCAs/{!-,#+....+%-;]/zsAHvGLvGHCByrF);-$%+.....&=-)FzyBBGGLLGvHCCBy^{'->*&.....@=-;{/xtCCCGLLvLGHCCxz]);,*&....&-;)/zyBCCvvGLGCCCAz/);-$&.......@$-;]/zyACvvLvLvCByzF)!-=#@...&=;)/sAHGGHHAAAAABHGGGGHAxr];#..+%-;)FrzstCHGHGGGGGHGGGts/);*....+#=;~]/zytBGGvGvvGHCtxzr/);$+...@#=;']rzstCGvGLLvGGHAyszF);*....@#,;)FrsACGvGLvLGvvGCBys/{;*+..%>;)FrxyBCGvvLGLGHHCAAsrF~-=%......&*-~)/rxGGLvvGHs/);>#...........@*,;)FzyHGvLLGHs^{!-#@..........%-'{/zxBGGLLLGHxz/);=#+........&=-']/zxBGGvGvHAyz/);>#+.....", +"..+@%*,;{}QKJwNwNNNwJKr:);-$%+....+&=-)][QKwwNNNNwJKI/_;->*&+.....&*>;(/QIJwwNNNwwwKr[{'-=*&@......&*=;)/IrKwwNwNNNwJK/}~;,$#&...+@*-'{/IKJwwNNNwwJJr[('->*&......+@*=-'][IKJwNwNNwwKI/('-=$%+...@=-_}POwwNNwJJzKJJwwNNwwJI[{;%..@%>-~|F^IPwwwwwNNwNwwNNY5}~-#....+#*-;(F[IPwwNNwNwNwwPI/}]~-%....@#*-!)}/IPwwwNwNNNwwJKQ/1'-#....@%=-;1/QIJwwwNwNNNwwJKI^]'-%...%*>;(FQIPwwwNNNNNwwJKI^});-*@......@#>;!(25wwNNNw6[_-=*@...........@#=-!(2IOwNNNwJ[|'-=&..........+@*-!)1QPwNNwNwJQ1~-=#&.........@%>-'(:[6wNNNNwP[|~->$&......", +"...@&$=;(}[<8OSSwSSOOPQ}(;-=$&.....%=3~1}Q5OYSwwSwOP<[(!-=$#+.....@*=;_}[Q5OSwSNSOOPQ[|~;-$#@.....+@*>-_:[QI6YSNNwYO6KQ:(;-=*@....@$>;(}[<8OSSNSSYOP<}(~-=$&.......@%=-_1}Q5OOSwSSYO6<[1';-*%@...@*-015OwRNSYO8KPPOOwYMRY65[:;*..@*,9_1[Q<6YSSRRMRSSSNNNY51~-%+...@*>-'1}[<6OYNNNwSwYOP53!:}[<6OwSSNwwSSOP<<[:~;$+...@$>-~1}#&...........+&>-;_|<8YNNNM64~-=$&...........@*=-;(15OSNNNR52(;-$@+.........+%*-'_(}5OSNNNR5}_;-*&++.....", +"...@#*,;de47iipppmpmppmgbd0-*&....+%=;a:7hhipppppmpppied0;=*@....+&$>cae4hiipppjjpppmgfda3>*@......@%>30|f7h5ppppjpmppi7bd;3*&....@*>cde4hipppppppjppheda3=#+......@%=-ae44hippppppjnmifba;,*@...@*3cbgippjjjoqqqjXppppppppmga$+.@*-afgppppjjjppippjqUTTqjed9*....@*=9dgijjjopjppijjpompppgfc=+...&*-0dgijppjjpjipijjjXppigbc=+..+@*,!bgipjojjjjpijjojopppgb0=...%>9afippjojppippjjojjjpied9,&......+&#>9cdfipqqTWjgd9>#&...........+&*-30dfimqWTWjgac,*&...........@#$-90deijqUUqjea9,$@...........&%=-0abgijqUUqpba;,*&.......", +"...&%>3a14klll5hilhmooomked03*@...+#-0bekkllhh5ihmXZomhe|ac>&.....%*,cb4kcd:47khhhhhilXoZXi7edc,&...+@>3a17kh5h5hihimoZom7eda9*&+.....&*>c(4kll5hhh5hmnooph4bdc=&..+&=3ab7lVVVXmqWUWWnmlhhhmmoqib>+.%,0bgpqZZomlggf4ghVjUTTTjgb0$+..+%*90bioZWqnmmhihhVVXZZWZngb3%..+%,9dfpoWZoXm5hhghhVnoZWZpgd3#..+%=3afmqZWonm5hghhlmnoZWqngb,@.+%3afioZWZnm6ihghlmnoWWWoiea3#.......@*=-9abfgjWUWqgb03=&............@*$,cabegpWUUjgbc3*&...........+&*,,0abfijWUWjgb;=*&...........@%$=3;abfipWUWjgb9,$&.......", +"..+#,;~]zyAACCCBBABBDDvHxsr/~-%..+#-'FryBACCBBtBBBGvDGAyz/{;-@....*-'{rsACCCABABAHHLDvtyz/{;>&....&#-;{/zxtBCABBtABGDLDAxzr]!-@..+%-)/zyABCBAABBBAvEvGtszF);$&...+&=-!]rsACCCBBABBAGLDGAyz^]'>@..#-~FrxBCCCCCDEEEEGABBBBCvEEwQ-&+*!]zADEELGCAsI^rzsAHuuEuuV/{,+..@=3~FKtEEELvCAAyxABBGDEEEvAr(=++#-!{/sGEEELHCtyyyyBAGDEEEvyr($+.&=3)FsHvEELGCBtyxAACCLEEEGyr'@.+=)/stEuELvHCAyxAAHGvEEEvxK]'=.......@#>3'(}rsGLEuvyI{!-$+...........@#>-'(FKsGuEuDy[{'-*@..........+%=-;!|2ryGuEEvs^{;-#...........@&=,;~|/zyvuEuGK^);-#.......", +"..+*>;)/ztBCABCCABBBGLvGAxz/);*+..%-)]ryABBCCBBBACHLLGBss^]!-#...+*;!]ryBBBBBABBBCGLLGBys/{!-#+...&$,;(/zyBABCBABBCHGLGBAsrF~-#..+*;)FzyBBCCBCBABHvLLHAxz^);-#....%>-~]rxAABCCBBBACGLLvBxsr]!-%..%;)FryBACCCCvEEELvCBtAABGvEA^;++$)FztHLLLGAsr/FF^rsxtuvGtz/)>@..@$;)FKALELLCCBAxyyACCLLELvtz]>++*-~]rsGLEEGHCBAxxyACHLLELGtr{=..&>;_/KGLELGHCCAxyACCCGLLuGx^;&.@-)/JALEELHCCBxyxBCBvELEDAzF'$+.......@$-;~]^JtLEEvxr]'-$&...........@#*-;)F^stLLuvyr{!-=&..........+&*=-;)]QsGLEuGs^{!>#+...........+%>-;)FIsGLEDHs/{!>%.......", +"...%=-;(F^QrKIKKPKPJwwwPQ}]);$&...&=-~]FrIKKKKIPIPwwwOI[F(!-*&....&=-!(//rIKKKIPI8OwwPI2F)!-=@....@&=-;)F/QrIKPKPIPOwwOP[/]_-#&..+%=;~]/QrKKKIKIPPwwMJQ[F);,*%....@*=-!(/^IrIKKIPKPOwwOI}F|'-=&..@=;_]/QrIKPJMNSNwJIIQrIIJMY81>+.%-_:-!)25wNwOQ|!->&@............@&$>-!([8www8[|!-$#@...........+%#*=-'(}6www6[(;-*%+............+%$>-!|[6wSSP}(;,$@.......", +"..+&*=;_:2[Q5688Y6YRMNM5[:)!-$&..+%=-;(}[QI66OM8MMMNM6[}|(;=*&....%>-'(}[QI686M6YMRNM8[21_;=*&....@#*-;(1}[Q868R6RMRNRO52|_;-$@...#,-_|}[Q58OY8OORRNR5[:__-=#@....@%$-;(}[Q<8O6Y6YYMNM6<2|(;=*@..%,;_1}QQI66MRNNNR8KQQQ<<8MM5b=..&-!(<8SSO<}(_;;-c!_d|:21(~;-%+...#=3a15RNNOPIQQ[QQQI5ONNS8[(c*..&*-c_e5RNNOPI,-_|5MNR8e_;>$&+............+@**--a15YNR5e~->%@.......", +"...&>,cdekhimqUUUTUUTTqjgfba3=&...%=;0b44iioqUUUUUTTqjgfbd0,*&...+#=3ab77imqWUUTUUTTUjgfbac>*&....+#*-0bekhipWUUUUUTTTqifbbc3$@...%>;de4himqqUUUqTTTUjgeba9=*@....@*=9ab4ghijqUUUUUTTTjgfe_0,%@..&-0be4hhmoqTTTTTUjmhkhlmjWqib>++&,0bgjWWqigbbdacaadbbefbbac-*...+#-cdfjqTTqpl5lkhhllmjUTUjgb9#..&*-0dgpqTUqpVlmlhllhmjWTUjgd;%+.@$-0dgjUTWqmll5hhlhlpqUUqpg0,+.+%3afiqTTWjVh5hhhllloqTUjiba3#.........@%*>3cfijUWjgd9-*#@.............@%$=-abiqUqjgdc-*#@.............@#*,3afiqUqpf09,$#@.............@$$,9afiqUqpea;,$%+......", +"..+#=caehlmnoqWqUqUqUUqjh7ed9=&...#,0b7hmmnoqUUqUqUUUqik4b!9>%...@$9a|7lVmnqWqUqUWWUqji7e1c3>&....&*3cdehmmXoqWqUqWWWUqph7:a9=&..+*3ae7lVmXqWqWWWWWUqjh7ed;3*&...+&*-0b7kVmoqWUqWqqUUWolkf1a9>@.+*0(43cdfpqUUjiba9,*%@............&#=,caeiWUUogbc9,*&@............@*>30dgpqUWjgba9=*&+...........+&*=-abgpWUWjgb09=#@......", +"..&>!{/stGGLvDDDDDDDDvDGAys^{;*..+>)/ztvLvDDDGDDDvDvvHBxsrF'-=@..%-)/sxDDDvvDDDvvvvDDGCtsr]!-#...+#-~FzyGGvLvGvDvvvvDvvHByz/)-%..@-)/stvDDLGvDDvGvvDGGCtsr{'-%...+*-)FzAvvvvDDvvDDvvDDGBtsrF~-#.@,/sABBCBCGvDEEuEELHCBCBHHvGx[;++%9)/sAGDvDvGHABBABABHGHGtAzF;+..%-~]QJvuuEEGHCBCBCCCHGGDDHyz],+.#-_FQsuuEuEvHHGHCCCCCGvDvHxr{=++*;_FKxuuEEEGBCCCCCCCHGDvvHsr!@.@-(/JGuEEEvHBCCCCHBHGGvvHAz/_>.......+%*,-~{^sGLEEvAK/)'-,*&..........+#,;~{/JtEEEvAK])'-=*@.........+@#-;~]rsGEEEvyrF);-=%+.........+&*-;~]rsGEEEGsrF);-=%+....", +"..%-!FrxGLLLvCAAAxAACCBCBts^);*+.+,)^JALuEGHBtyytBACBCABszF);=&..&;{/sHGLLvHCtAyytCHCBAAsr]~-*+...*;)FKtvLLLvCBtAxAACCCCAxz/);*..+,{rsHLLLvCBAtxAACCCCAAs^{!-*+...*;)/stvLELGBAAxtBBBCCCAxzF)-%.@-FsBCCCCBCtALEEEEvBCCCCCCCAy^-@.#-~:rxBCHvvLGCCCCCCHvvDLGAs^-@..&-~]^sGEEEEvCCCCCCCBBCCBCAsr{=++&-~FrsGEEELGCCBCCCCCCCCBCtsr(>++#;)FryvuEELGCCBCCCCBCCCCCBs^'&.+,{^KvuEELvCCCCCCCCBCCCCAxz/)=+......&#=-;)]^JtLEEvAz/{~;,=*&........@#>-')F/JALELvAzF{!;-*%+.......+%*=-;)FrJtLEuvxzF{'->*@........@%*>-;)FrsGLuuGy/F)'-,*#@...", +"..@>-~1[8wwwJ<^[/[/QIKKI^/F|!-%..+*;(2PwNNOKQ/[/[^IKKKrQFF(;-*@..@=;(28wNwOII[/[[[KJKK^^/1~-=%....&=;_2&.+=)}rIKKKIrQQ6SNSNOJKKKKKKKI[)$+.@#>;_F[II8OwJJJsJzKJJOwJPQF)=+..+*-!([6SNSNJJKzKKKzKKKKKKI[|!*..@*-'|46SNSwJJzKKKKIKKKKKKQ}{!%.+@*-!|7ONSNwJKKzKKzKKKKKKIQF_-+.+*;(}5NSNNwJKKzKKKzKKKKKI[]);*......+&**,-;'(2PYwwO<}(!--=*%@.......+@#=-;!)1[8YNwOQ:)!-,=*&+.......@%*=--;'|[6Mww8Q1_!-,=$%+.......@&#>--;~1[6www6[|_;--=#%@...", +"..@*,0(48SSR8[}}:}2}&..*;:[QIII3!(|}Q56OJOO6PI,9~((f5MNR6[:(~;-,$*@.......@%*>-;~_1}5RNR841(!;-,*#@......+@#$,;;_(|45MNM84|(~;->>*@...", +"..@*,0dgpqWqji74777hppplk74ea3#..+%3afiqWWqig77ggiijjpih7eb;3*&..&>cafiqUWqih7777gmpjph47ed0,#....&=9cbgjUUqph7777gipjjih74ba-%..+$cdgiWUqji47777gjjjmh77edc=%....&=3afiqWUqig7777hijopik74b0,&++$0ekimpppmipjTTTUqjmmhhhmmiga$...@*$>3cabf4imqWZomlhhh74ba0,#...@*=30bijjqjopmlkhhlhlmpjpied3#..+#=9abijqjoopmlklhhhlpjppied9#..+%=30bijqqojjpikhlmppojopgf0,+..%>0bgjqqqjoXmklhhlhmjjjigbc3%.....+&*>;abbbfgjqTTqigeeb~c3*%......+%*=;0abffgjqTTqigebdac9$&......+%$-cabbefijqTUji7fbba0-=@......+#*,;abbffijUTqqifeb_a;,$&...", +".+&=901gnWUWZmVVVVmnoWZoXVl7bc*+..$cbemqUUWoXVVXnnqUUoXVlked9>%+.@>0bgpZUWZnVVVVVmoUWqnVlkea,*+...#=cdfiqWWWnmVVVVXXWUWXmVk7d9#..+=0bgjWUWZXVlVVVnoUWqXllkba3#+..+#30dfmqWUWnXVVVlmoWUqnVVhed3#..=d4kXnZZWqWWUTTTUWZZnXVXnooib$+..+&*=,30(ekVnZWWZnVlhk771ac=%...@#>30b7mnXoZZoXXVVVVXnWWWjgbc%..+%$30bhVnnoZZoXXVlVVnoWUqjgb;#..@#$90bgmmXoZWZnXlXXZZWWUqp7d,+.+#,3dfinnXZZonXVXlVmoWUUqiba-#.....@#>9_e4khlmoUTTUoVlkk7:a9#+.....&*=0d:7kkVmqUTTWpVhkk4ba3#+.....@#,9de7khlmqUTTWnVlk72ba3#+.....%*9cb27khlmqUTUqnVlk74ba,%...", +"..#-']rxvEEEGHCCHHHvEELGHCCxr]=+.+=(^stDEEvvHHCGGvDEEDHHCtz/),*..#;{^stEELvvHCCHGvDEEDvCCtz/'=@...#3~/ztDEELDCCBCGGDEEEGCCAy^)=..@-]rsGLEEGHCCHGHGvEELvCAAz]'=+..+*;_/zAEEELvHCHHGvDEEEvHBAs/'$+@-FsAGDLELELEEEEEEEEvGCHCGDDxe,....&=-;!)/zyBvLEEvGCBCAxz^{~-#...@$-;(^sACHLLEvGBCBCHHvEEEDJ/)*..@*-!|^sACHGvLvCCCCCCvDEEEDP^(=++&=-9_}ztCHGvLvHCHGvDEEEEEwse-+..*9_FzxCGvDLLGCCCCHHDEEEuwr{9$....+#-'{^sACBCHvEEEEGHCCAAs/);&....+$-!]rsACACGDEEEEGHCCAAs/)-&....+$-~]rxABCBGDEEEEGCCCBAs/),&....+*-']rxABCBHDEEELHCCCAAz/~>+..", +"..%>'{^KAGvvHCCCCBHHGGGHBCAyr(=+.+=)FKAAHGHCCCCBCBGGGGCBBAzF~;%+.@>)FKyBGvvBCCCCBHHGGGCCCyzF'>+..+%-']QyAGvHCBCCCBHGGvGCBBAs/)$+.+-_/sxGvGHCCBCBHHGvGGCCBxr]'>@...%-~FKxHGvGBCBCBHBHGGGCCCAz/'*++>]zyBHGGvGvGvDvDvvGHHBCCCGtz{$+...+&*>;)]rstHCvGGCCBysr/]';>%...+%$;)FryAAAHGHCCCCBCCGvHvAzF;%...&$-)FzyAABHHHCBCCCCHHGGtxzF;#..@#=;']^syACHHHCCCCHGGvvGGxz{-+.+#-!)^sxABHGvHCBCCCCHvGvtJ/);*+....*-!)rsyABCBHvDvGCCABBtsr);*....+$-']rsyBCCBHvLvGCBCBBxs/);*....@$-~]rsxBACCGvLvGCBCBBts/);%....+#;~FrsyBBCCGvLvHBCCBBtsF)=+..", +"..+%=-'1}QQQIKIKIKKKIIKKIrQF);*..+%>;):[[IIKKKKKKKKIIIIrQ/|!-=@+.+%>;(}[QIIIQIKKKII@......+%*>;)]2IKJJKKI/F{~;-$#@+....+&*>;~(]22QIIKKIKKKIQ[4e1d;$+...+%*,;~)]2^QKIKKKKKrQQ[72b';$+..+&#$,-'){:[QQIIIKKIIIQ4}e|_,*....%=;!)]F}[QIIKKKKIIQ442e(!-*@.....@*-;)]F^QrIIIIIIKIrr^/F_;*@.....%=,;)FF^QrIIIIIKKIzr^/])-*@.....#=-!{FFQ^KIKIIIIKIr^^/])>$+....+&=-!{F/QrIKIIIIKIrrr//]'-*...", +"...@%*-9(1}[}Q[[Q[[QQQ[[Q[}(',&...@*,9_|2}[[2[Q[Q[[}[[[Q}:~->#@...+*-c_|:2[[[[[Q[Q[[}[[[}|!-=%+...+&*>3~(:}[[[QQ[QQ[[[[[[2:_;=&...&*-0(12}[[Q[Q[Q[[[[[[}}(!-*%+...+*$,9_|22[[[[[Q[Q[}}[[[}|~3*&..%,;_(}[[Q-;(1[IP6IQ[2('-=*%+........@#=--;'(:}2[[Q[[[[::(__c-#+....+&**-;;~|2[Q[[[Q[Q[}1__!->@.....+&*==-;~(:2[Q[[[QQ4:|_~;3>&....&**--;!(:}[[[[/[Q[}:(_!c-*#+.....@#=-'_|}}[QQQQQQ[Q[221_!-$@.....@%=;'_|}}QQQQQQ[Q[[}}|_;-#+.....@$=;~(:2}[QQQQQQ[Q[}}|_;-%......@#=;'((:[[QQ&+..", +"...+%$-;abeef777k7777744f4ea;=%....%$30dbe477777774fff44eb'-=%+....&=30bbf47777774ffefffebc,*@.....+#$,00bff4777777fffef4eb03*@...@#>9abee44777774feee4eed;,$&....+&*$30deef47k77774ffffeeba-*&..&>9cdb74477ffbdbbf47774fb0c=#........@#$9abfipoomhfba;=*&+.........+@#>>39abef7477774bba09,*@......@%#$,9;db47777477feda0c=$&......@%$*=39abef77k774fbdc03,*&.....&#>>,30dbf4777774ebdac->#@......@%=-0d(b47777777747f:bdc,%......@%*-0ab:f7777777744f:bdc,#......@#>3addee7777777744eebd;>#......+%>30db:f477777h47fe:bdc>%...", +"...+&*=9adbee4477744eeeeeeba9=&....&=3cdbee447744febeeeebdc,*&.....%*90dbee4474744ee:eef|dc3=%......@=,cdbbee4444744ebeeee|09=@...@*=3adbeef477744eeeeeeba9,*&....+%#$30abeee47777eee:eeeed09*@..@*,c'befeeeebdddbbeeeee:d03=%+......+@*,;b7lnZWWXie1c3>#@...........@&%=>90dbee4444febda;9,*@......+@#*>,9abee4444efebda;3,*@.......@&#$>9adee444eefebac9,*%+.....+&#=,9cdbee4774eebda03>*%+......+%$30adbe47k77k774eebbac,%+.....@#>30adbe47k7k7k44ef1bd0,#+.....+%*-00dbe47k7kk774eebbd0>&......+#=30adbf4k7k7k7k44bbbac=&...", +"....@$-;){]F]^rrzrr/^FFFFFF)',%...+&=;~{{]/^rzrrr^^/]FFFF)'3=%.....#=;~{{F/rrrrrrr^F///FF{';>#.....+&=-')]FF^rrrrr^///]FFF{);=@...@#-;~{]]/F^r^rr/^F/F/FF)'-$@.....&#$-'){{//^rrrr^^FFFFFF{';=%..&=-;~{FF/FFFF(({{]/F/FF](!;>&.......+#=;{rxBGDvDvtz/);-#+...........+&%$,-~)]FF^^F/FF{_);3>#@......+@%#>-;~{F/rr^//FF]()!3$#@.......+@#>-;~)]]///F/FF{)!;-=%.......@#$-;!){{F/F//]F]{)';-*&.......+&$-')){F/^rrrzrr///]])!-%......+#>;~){]]^rr^rrrr//FF{)~,*......+*=;~){{F/rrrrrrrr//]])'-#.......%,;~)]]F^rzr^rrr^/F]])'-%...", +"....+&*-;!')))(FF{{{{{)))'';>*@....&#=;;~~)]F]F]]{){))'~!'-,$&.....@$--;')){{{{{{]{){))~~;;-=&......&#>-;!~){{]{({{{{))'~~;;>%+....@%=;;!~~){{{]{]{){))~''-$%+......+#,-!~~')){{]{]{)())~'!->#+...%=-;!'_)~)'~'~!''~~~~'~;->*@.......+%=-)FzxBtHGyz/]~;=#+.............+@#,;!'))){{))'~;--,%&.........+@#=;;!)){{{)))~~;;->%+..........@#$-;!~)))))))'';-,$%+.........@#=-;!)'))))))~;;->*&+........&#=--'!_){{(]{]{))~)!'-=%......+@#$,;!!)){{]{]{{{))~'!->&+......+#$-;!~){{{{{F{{){)~!;->%+......@#$;;!~_{{{{(){{))~!!;;$&...", +"......+@#==>-,,-----,->==$*&@.......+@%%==>>------,,=>>==**&++......+&%#$=,--------=>=>==*%&@........@@##$==,-,---->-=>>==#%@.......+@%===,=>----,->,===***@+.........&%*===>,,>,,---=====*%@......@##=>=,=,=====>=>=,>=>*%@..........+%*>;~(::|:b_;-=#&@.................@*$=>>>,>=,=>$%&@.............+@%$==---->=>==$%@++............@@@&*===,=>==$*%&&+............+@#*=>====>==>$%%@+...........+@%**>=>-----,-,-==*=*+.........+@%*=>=>->--,,,,===*$%@.........++%*===,,------>>,=*$&&+........++%$>=>----->-->===$#%@+...", +".........+&&&%&&%&%&%&&&%&+............@+&&%&&&%%%%%&%%%&&+...........++@&%%*#%%%&##%&&&&@+...........+.+@&&&&%&&&%&&#%&&@+...........++&#%%%&&%&%&%%%%&%&+............@&&##$#%##%%#%*%%&@+..........&&@&&%&%%&%%&%&%&&&&&+.............@#$=--3;,-$*%%&+..................++@&@%%&%%%&@@+.................+@@&@&%&#%&&@+...................++@&&&#%%%@+++.................@&&%#%#%&%@@+................+@%&%%&%%%%##%%%%%@@+............+@&&%&%&&#%%%#%&@+.............+&&&&%%*#%#&&&&@&&.............+++@&&%%#%&&##%#%&&+......", +"................+.+.++..........................+..+.....................++.+..++++.+.......................+.+...++..+.+.................++.+.+.++.......................++@@++@@@@+@++..............+..++.......+......+...............++@%%%#%&+++........................+..+.+..+.......................+..+.++.+............................+...........................+.++.+......................+++++.+.++@@....................+..+++..+......................++++.+++@@+++.........................++.++............", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +".........+..+++.+............................................................................................................................+.+..................................+.............................................................................................................................................................................................................................................................................................................................................", +".....+@&#%##***#%*#*#*####&@+................+@@@@@@@@+........................................................+@@&@@++....................@%%#%###%@......................+@@&&%%&&+++++......................................................................................................................................................+&&%%%&@+..................@@&&%&@+..............................................................................................................................................", +"....+@&&****==,===*=>=**%%%@+..............+@@&&&&&&&%&@+................++&@@++..............................+@&&%&&@@@..................@&#**=***#&+....................+@&&%&&&&&&&&@+................................................................................................................+@@@@@@..............................+@@&&&&&@@+................+&&%&%%&@@+...............................................................+@@++...........@%######&&+..................................................", +"...+&%#$$>,,--;--------=$$##@.............+&&####*#*#%#%&@..............@&#&%%#&@+............................+@&%##%%#@&................@&#*$>>,>=*#&+...................&%#$#$*$####%%&@...............................................................................................................@&%%##%&............................+@%##*#$*#&+................@&%%*#*##&%@...........................................................+@%%%&#&@.........@**=>---=>*%@.................................................", +"...@#$*,39;ccaaa000c0cc;,=**&............+&##*==>>>>=***%&+...........+&%#$$$*$*&&+.........................+@@%#**=>*$%&+..............+&#$=33999->$*&+................+&#$*==>,,==*=*$#%+............................................................................................................+@%#*=*=$*&+..........................&%$*$>=>>*#&+.............+@&*$>>>>=>*%@+........................................................+@#*=$>*$*&+.......&%*=3;acc93>$*@................................................", +"..+#=3cad:beee4e:e1eeeebdac3$@..........@&*>3ccaac09cc9,>**@..........@%=>3cc03,>*#@........................+%$=,-0aac3=*%@.............%$,90~be:bdcc,>#@...............&*>3c'adaaaccc33,**@..........................................................................................................+&*$-9c;c3,=*%+.......................@*>3c9cc0c3>*&.............@#=,99aaac93>*@.......................................................@&*=,caaa9,>%+.....+%=-0dbfee|acc=*&...............................................", +"..&,!]/zxAGAGAGAAAAtABttsr^{;$+.......@%=-;']/rrzrr^rr/{~;-=%.......+%=-!)FrrrF{~;->%+.....................@$-;!)FzsyrF'-,*@..........+%>;)]rsyAHysrF);,*&............+%$-'{/rssszz^//FF]~-=%+.............+....+@&&%&@+......................+@&&&@+................................................+#>;!{FrrrF)~;>$%@...................@#>;)]F/rrr^{!-=%+.........+%$-')F/zssz/)!;=%....................+++..+@@@@@@+...................&#>-;~{/sys^{!-*&+...&,'{/stGAAAsrF)-=%+......................+&%%%&%&@&&&%&@@+......", +".+#;)/zAGLLLELLGCCCCGGLCByz/)-%......@#=--!]/sttyxysssz/)';-#&......@*-')FrstxrF)~;-$%+...................@#>;~)FIyvGy/);-=%..........&$-!{/sAGLLLAs/]!-=*&...........&$-!)FzstABAxzzrrr/]',#@........@%###%#####**$***#&...................@##**$**#@............................+@@+...............&$-!)FzytsrF)!;-=*&................+&#$-!]/zsytxs/);,*@........+%=-;)FrsAABxzF~;-$#..............+&####*####=$$$$*#@+................&*=-;'{/KAGAsF~;-*&...@>']KwuEEvCAs/{!-=#@..................@&%##$$$>*=$*$>**$*#&+....", +"..&,;(}IJwNwNNNwJKJJwwwJI/F_;>&......@%==-;~]/^r^^/^Q/F{!-,$%+......@&*-!)F/r^/_~;->*&+..................+%*=-'~(}5OO<1'-*%@.........@%*>-~12PwwwwP[]~;=*&@...........&*>-~{F^rKrr//}F{]_~-$%+.......+@%*********>*$$**##+................+@&**>*>***&+........................+@@@&&@++............+%*,;~)F^rQF_~;->**@...............+@@**>-~]F/^rr/]'-=#@.......+&%*=-;)F/rKzIF(!->*%+............+@%*******$****=**#&@+.............+@&$$=-;!1[8O6[_;>=*@...+*;~}6wNwwJK/{!-=*%&+.................@@%#****$$*$**$>****&@....", +"..%=;_:[8ONNNNYOPIKPJwO6Q2:_;>%.....+&$$--;(1[Q[[Q[QQ[:(!-=*&+......+%$,3~|}[[:1('-,>*&..................@%*>;!((2<66<(;,$%+.........@#*=;'(}>,,,>>==,,=>$*#%@...............@%$$>,--->$$%+.....................+&%%%%%%%%&@+..........&#$,-!_:[[}1(_'-=$*%&+............@%%#$>-;_(2}[[[[(;-$#@.......@&#>,-!(:}QQIQ}|';,$$&+.........+&#*$$>>,,,>=>>,,,,>*##@+..........+@%%#*=--9_|46O54_;->*&....%-~b5RSYOPQ[('->$#%&+...............@###$>,-,,=,=,>,=,>>**&+...", +"..&=cafgmjoqqWqoojpooomi7f|a3=&....+&#=,;ad:fk*@.......@&#>-0be77efeba;>>#@................@#$=-0dee7ipigd0-$#+........+&$>30bbgipjqpigeba3>*&..........&*=-0dee75hl5iggfebdc,*&......+@#$=,9c;0c9939c0cc3,=*@+.............+%*=3c0aac9=*#@...................@&%%###******##%&@......+&*$-90db7774ebba;,>=#&@.........@@%$$*=,9abf7<774ebc-=*%+.....@#$=,30dee4khkh7eba;=>#&+........@#$=3399;ccc339caaa03*$#@.......+&&%%$$$>,;abbfgipifdc9>*&....%,0bgjqjplh7bac3=$*&&+............+@%$*==c0aa0999900c999,=#@...", +"..%,cb4hlXXnnooqWWWZqnXlk4bac=%....@*=-0be7klVVXooZnmVke_9,*&.......+%*,9abeQkllkkedc3=#&+.............&%*,3ab4khhlihfdc3=#&........@#=3;d4khVlmmmllk4(a9=%+........+&=3cdekVVVXVXnXni4:b03$&......+#>-0a(b4ee(ddd|e2bdac,$&+...........@**,0d|e444d0,*&+................+#*$=>-33-333333,==#+.....&*$-9ad|e[kkhlk7ed093>=*%+.....@%**>,39;ad:4klVlk7e1dc3>#&....+#=3;adbekIlVlVVlk7edc3,**@......@*=,;!dd:e4:b(ddfgigfac3=#+.....+&*==>,3330dekhlllih4ed03,#+..+#,cb7imXXVlk4b_cc3,,**%+.........&%*>,99abgii7bdd1e4ee{da03*+..", +".+*;]rsBBCBCCBGvEEEEEGHAxsr/);*...@$;'{/sABACCHGvLELGCAz/);-=@.....+%$,;~FrsxAABCByzF)!-$#@..........+&*-;)]rstCBCCBxz/)'-*#@......+#,;)FryACBBAtBBCByz/{;=#@......+#-;)FrsACBCCHGDLvAsr^{!>%.....&#>;)F/zxtAyzzzsxAAxz^])-#@..........+*,;~FrstBBtz/~;=#@.............@%=-;;~){]]]]]]]]])~!;$+..+#=;;){/rzsxACBCCAsr/]{)!;,%+...+#>-!~{]]F^rstCBCAAyzz/F)';=&...&-!)F^zsxACCCCACCBBxr/{~;,$@....@$-!)//zsxyBAszzzyGDGsI]);-%....+*>-'~)){]F/ryACBCCAAsz//]{;#...*c_FQtAAHCCtyzrFF]{)!;-=%&.....+&$-;~)]F/sHvGysrrsxBBxysr/{;#..", +"..*!)^syBCCCCCCGDvuvvGBCAyz/];*+..%,!)/zAACCCBBGvLvHHCts/{';>&......&=-;)]rxBCCCCBAsrF);-=#+.........&*,-!]/zyCCCCCAyzF{!->%@......&$-!{/zyBCCCBBACCAyz/{;-*@......&$-!)FzxBCCBCHGvvGtsrF{;,%.....%$-~]/rsyBAByyxyBBAyzrF);=&..........&*,;)FzxCCCCs/);-*@...........+&#=-;'{]F^rrrrrzrrr/F{)-%..&$-!)]/zssxyyABBBBAxzr/]]~;,&...&>-')]/rrzzssyABABBxxsz/]);-#..+=;)FrzsyABAABACBACCAsrF]~-=%....&=;)FrssxBBAByzzzxtGGAz/]);$@...&>;~)F///rrrzyABBBABtxszrrF),+.+*-!{^sACBCCCxyszrr/F])';-*%.....%-;){//zsytGGtzzzsACABtAsz/)=..", +"..&,;)]/QKKKKII<$@......@*=,;~]}rKzKzKrQ/|~-,*%@........+&*=-;)F/IrKzKKr[});-=$%@......@*=-!)]/QKzKKzKzKr2])!-*#@......@*>-;'{/IKzKKJKJPKQ1(';>$@.....@%*-'){F/rrKrrrrzr^F]_~-,*@..........@%$-!)2IJwwJI1!-=%@...........+%*=-;~{{}//[/[/[/[/}F_);%..&*-!)1F[/Q//F/////^/^[F:{~-=&...@=-;){1/[/}FFFF//^QrQ/[F|~;=%...$;)|FQrrIr^/^//^rrKrQ/1)',*&...+%=;~{2^rrrr/FF1::[,;)F/QKKzKrI/Q^^}](~';,*&....+%>-~_F2[QII5<[1]F//rKrrrQF{~>..", +"..@*-;_1[QIQQQ[[[24<8YY6-;~12[QIIII[[}1~;->*&+......+&#$>-'(}[QIIIIQQ[:~'->*%@.....+&*,-!_(}[QQIQQIQQ[}(!;,>*&+.....@#*=-'(1[QKIIIII,;~(1}QQQQQIIQI[1(';-$#&..........@#=-;(:QPYS8<(;=$#@..........+&#$>-;(1[[QQQQQ[QQQQ[}:(;*..&*-'(2}QQQ[}||((|1[[QQ[}:~;>%...&*-'_:[2Q[21_|(1|:2QQQ[21~;=&..+%;(::[QQ[[2::|:12QIQ[[2|!-=%+...&=-_|2[QQQ[}1(1(1:[QQQ[}:_;=%...@$;__}[[QQ[}21|||:2[[QQ[Q}|_>+.+&*=3~(}2QI5KIQQQQQ2}((~;->#+....#=;_12QQQQQQ[2(((1[[QQI[[[:_=+.", +"..@*-cde4hlhh77ff47gjqWjmh7ed3#..+%=9d:4hhlhk447hhhhhk4edc-=*&.....+&*,;0de4khhlllhh4ebcc,=*&......+#*,9;a:77hhlllhh4fbac9>$#+....@#>-cabbehkhllhlhh7fbac3>$%+....+&*>-;abekhllllllh7fbda;=$%+....@%=30db:7kkhl5kl5k7eba;3>*&.........+%*=9abfijWUqib0,**&+.........&#*-90abekkh5hhlhhkhhhkfba*+.%*3abf7+..@*>-abe44lmmhhhhkhk4bbba;>*@....#>cde47klhhh4fbdbe4khhlhh4ed>+.", +"..@*30d4kVVllhhhhhhmqWWomVl7ba$+.+*-0b4klVVVlhhlVVVlVlh4ed0c,#+....@$9cd1e7klVVVVVVVhke:d0c>$&....+#=9cd|e7lVVVVVVlVVk4e(a;3*&....@*,cd1e[hlVVVVVVXVVk4e1ac3$%....@#>9a|e4klVVVVVVVVVk4ebdc3$&....&>-cd147hlVVVVVVVVlk4eba3=#+.......+&*>3c_ehmoWWqied;3>$#@.......@%=30d1e[hlVVVVVVVVVXVVlked$..&,cd}klVXVlk41b|bekhlVllk4b9$+.+&,9(eklVVVh741b|beklVVllk4b9*...=a1khVVVlh7eeee}klVVlVk7e'3=%...#-014kVVVVh72e:be4hVVVllked9#...@=ab4;'{]^zxtHvGGxK^])!;,=&+....@$-;{/sxBAHGHCCBCGGvHBCCCyzF,++*;{/stAGGGHAssrrrsyCCCCCCyz]-%.+*;{/sAHGGGCAxzrrrsxACCCCByr(-+.@-FztAvvvHAAsszzsxCCCBCBAz/~;#..+$!]rsAGGGGAAszrzzxtBBCCCAx^)=...&-{^sAHvGGHCtyszzytBCHGvvAy^'@..%,;{/sxBHGvHHCCCCBCHHHGtsr]'$..+>!]rsAHGvHHCByszsyACHGvvHAxr'#.", +"..&,;)FzyCCBGvLLLLuEuEEEELGGsF-@.%,!]rxBGLLLLLvGCCCCGLvCCAxr{!*..@*-~]zyCCCGLGBCCBGGvGBCByr]!-%..&$;~FztCCGGvGCCCCGLvGCCAs/);>%..@=;{/sACCGGLGCCCBHLvHCBBs/);=+..&>;{/yACCvGLvHBCCGLGCCCAy/{;$@..%-;]rxBCBGGLvBCCBGLGCCAAzF)-*....@#$=;!)]/rzsytBAAsz^F)~!;,$@....%>;~]zyCCGvvvHCCCCLLvCBCBxz{-@+*;)/sAGvLvHByzrrrsyABCCCBtz]!%.+*!{rsAGvLLGBysr^/zyCCCBCByz]-+.@-]zyHGLLGCyxzrzzyACCCCCtsF);*+.+>;]ryBvLLLHAyzrrzsACCCCBByr(>+..@-(/sAHGGGBCCyszsyACCGvDGAs/;@..#,;)/zyAHLLLHCCCABBHvDuvAsr{=+.+$!{/stHGGGCCCyszsyBCHvvGGBsF;@.", +"..&*=-~{/IKKwwNNNNuNSuNNNNwP[_>+.@*,;{}IOwNNwNwwJKKJwwwJKI/]_-#..@*>;)/IKJJwwwJKKKJwwwJzK/:~-=&..@*,;)/IKJJwwwJKKJJwwwsKK/|;-*@..&=-'{^KzJJwwwJKJJJwwwJzI/{!-%...&=-']/IKJJwwwKKKJJwwJJzI/{!-%...@=-!{/KKJwwwwJKKJwwwwsKz/_;>#....&%$=-;~)_{]2F[/[/}F(()';->$&....@*,;)2rKJJwwwJKKJJwwwJKKr}{;#+.&>;)2IJwwwJI[F(((]^IzKzKK^]',&.+&=;(}IJwwwJI[F|{(F[rKzKzK^{;*..+>_:[PwwwwKQ/|{]][rKKzKKr});=&...#-!([KONNwPI/}{){F/IKzKzI/(;*....#-'(}IPPPKKK^/}F/QIKPPPI[1'$...@$=;)F[IPwwOI[^[^[QIPPJPIQF'=...&=-~1}IPPJKKrQF2/[QKPPPPI[1;$..", +"..@#>-'(}[QI6MNSNSNNNNNSNSY6}_=@.@#,;_:<8wNNNww6KIIIOwOPIQ[:'-%..+*,3~}QKIJwYOPIQI8OOOPII[:!-*@..@*=;_2QKIJOw6IQQIPwJOPKQ[1;-$@..@*-;(}QIKJwOOIQQIJYw6PII2(;>#+..+$,;(2IIPOwY6IQQ#+..@=-'12QIKJJw6IQIKJwOP5KQ}_;>%....@$=>-!_((|::::e::||{(_(';-=%....%=-;~}QIIOOYJPI-!|25MwY6[1((___:QQIIIQ[|;=@..%=;~:[6RwO6[}(_~_(}QQKIKQ}(;*+.+*;(}5Oww8<}|(___:[QIQKIQ:!-$&...#=9~:-'1}QQIQIQ[2|}}QIII&..@$,cd7immpojmh47hmpoppmm7ec3>@+.&=cabgijppommhh7gmjqjpmm4ba-*...@$3cb7lmpjqoph47hppoppmi7b0-%...&>9degmppnommk77ipooXpmi7bc,#...+@*39cbffggggggggggggfgeebc,#....&=9ab7impoqqpmkhljoqoommkfa,%..@>30beiooomgebdad(e7hhllk4b;=@..&$3cbgmoooi7fb_ddbe7hhllk4b3*..+*cdfijqophfbb0ad:4hhllk7ea3=&...%>9abgmoqoigebdadbekhlllhe_3#....@#=-cbe7hhlih7ff7iilh74bdc,%...@#=3abf7ipji7bb(bbefhhhhh4fd=....@%$9abe47ili44ee7ilih7fbd9$@..", +"..&*9ab7ioqooojjjiipipjWUUUpgb,+.@%>3a1kmnnZnnXlh47hlXXXVVl4b9*++&>9deiXoZojmik477hmnoZoomgba,&..&,cdfiXZZnXmik777kmpoZZop7b0,&..@,cb7moqZommh4474kmpoZoom7b0$@.+&-0b7mooZopmh7777hmnoZZom4d0=+.+%-ab7moqZnpmh777khmpooZnifdc$+...#-!bfijjjojqqqqqqojjjjjjif0=+..+#,cdeioqZoppmVVVXnZZZZqomhb0%..&*9cb7lnnnVh7e|bdbeklVVVVkea3&..&=9~b7lnXnlh4ebdbb}klVVVlk:0*+..*cdehXnnXlke1bbbe7lVVVVl4dc>%...%=90:4mnnXlk4ebdb14klVVVVkb0*....+&$>c_bekmnXm5hkipoph4bdc9=&...@*>9a:4kmnnmh4eeee4khVVVll7d>+....@*,0abekmXnmhhhlnnmh4bdc3#...", +"..#-~FrxGvEEvGCAxJsJxAGvEEEvxr!&+&*-!]^sAHHGHHBtysssABCBCCCyr(>+.*!]/sHDELDHxsszzzsyAGDEEvtzF)$+.%;]rsGDEEvHtsszzzstHvEELDAzF~$++*!]rsHvEvvAyszzzssyCvvEEHyz]-@..*'FrxGEEvvAyszzzssACDLEGGxr]-@..*~FzADEEEvtyszzzzstHvEEvHsr]-@..+>'/syvuuEEuEuEuEuEEEEuuuNs4-@..%-!]rsHDEEvHBCCCBCGHDLEEEvAK]-.+#,')^stHBHCAysrr/rsxBCCCCtzF;%.+#>;{/stGHHCtxzr^rrsxACCCBAz],+.+=~/ztHHHHAysz^rrzxtCCCCAs^{;*..+#-'{^sABHBCyyzr^rrstACBCCyz(>+...+@*,;~]/stvGGHvGGDGGyz/{;-*@..+#>;)/zxAGvLGAAyxyxyAGvHHAtsF-+.....%>;)FrsAvGvGHvHGvAsrF);-#...", +"..%-~]zyGLLGvCxxsszzsstvLLLGy^;%.+*-~]rsCCBHCCBysszsxAACCCCyr),++%!]rJtLELGAssrrrrzsAGLELLAz/)>++#!{rJtLELGtyzr/rrzstHLELutzF)$+.*!FrxGLELvAszrrrzzstHLLuvxzF;@.+*!FryGLEDDyszrr/rzstHLLDGyr{-+..*'/KxHLLuGAszr/rrzsxvLLLGsr{-+..+=~FzxAGHuDvuDuEuuuvuDDvDAz]-+..&-~]rsHLELvCCBBCCCCBCLEELGtz{=@.&=;)FzACCBCBxsrr^rzytCCCCAzF;#+.&-~_/zACBCAAszr/^rzyBCCCBAz]-@.+>~]zyBCBCBxsr/^/zsyBCCCBs/);*+..%-']^sBCCCAAxzrr/rsyBCCCCxr{-.....+%=-;)FzyCGvLLLLGHts/]~-*@....%,')/zyBGLLLBBABBBAGLLEvByr]>+.....@*;!{/zyCCGLLLvGCAzF);-$&...", +"..@=;'1Q8wNwPI2F||]1:2Q6wNw8[(,@.+%$-;{2rKJKKQ^/]]]:F^IKKKK^{!*.+&-'1[6wNwO<2]((((1:[PwwNO<1_-%..@-!{[8Yww8Q}{1((({:[PwwwO<:_,%.+#-~1[8www6Q2:((({1:[PwNw8Q|;=+..&-~1Q6wNwP[}](((({2[6Sww6[1~>..+%-):QOwNw642{()({:2Q6wNw6[(!=+...%>;_|e472<<<<-']QKKKrr/2{()|]/^QKKKr}~-@..@*-;):QKzKI^/]1(({]F^QKKKQF_=+..&>;1[KzKIr2F1){)]F/IzKKIF{!-%...&*-;)}IKKKr^}]((({F2/KKKz/];#.......&*=-;1[IKwwNNwJKQ](;-$+.....@*-;)2[QPYwwJKKKrIKJYNwOI}(;*......+@*,;_|[IJJNwNwKI[|!-,*&+...", +"..&*-c(46YSY5}1(____(|}5YSM52_>+.+&#=-'1[IIIQ[}|(~_(:}[QIQQ2_;%+.@=;~:5ORY8[|((~___11+..%,c(}6YYY54((_____|}8YSY54_;*+..%-c146YMO52___!~~(128YSY5}(;$@...@*>3c!a_|b|||1::1|||d__!;-$&+..@$-c_15YSSO+.+%=;_}QIIQ2}(____(|}QQIQQ}_;=%...@$=-~:[III[21____((}2[I;~|[<6RNSO5[:(;-$%+.....@%*-!(1[5ORYO8865868OOO5[|_-&.......@#*>;_:2@.+&#=3ab7hlhh4fbdaad:e4khlk4bc*..&=cafiqWqpgbbd0aaabfiqWWjgba-%..%>cdeiqWqpgbdaaadabfiqWWjgbc,%..&,cdfjqWqpfbdaaaadbfiqWqpgbc$+..&,0bgpqWqigbdaaaadbfpqWqpfbc*+..&3cbgpqWqifbaaccadbgpqWqigdc*....+%*>,,30aadbbbbbbbda033,-=*@...@*>cdfiqWWjihhhllh77gpqWWjgbc*+.+#*-cd4hlh<74ed_ad:f4khlk7e03%..@#*3cb7+..#9abhoWWZp7f:dd(befhpWWZp4b0$+....@%#=,390dbeef77febdc9,,=*#@...@*30bgmWWWZXmlmXXVhhhpWUWoifa*..@%>9a:kVVVlhk4e1eeekklVVVkebc*+.+*>-a:kVVVIlk4eb|e}khVVVVked>@..%,914llVVh74e:b1e[kVVVVl4ba3#...&*>9a}kVVVlk72b::e4klVVVVk:a>+.....+&#>-0d1kijUTUoi4da9>#&......&$=c_e7hmqUUUTUUUUWoVlh7eac=&......@%$=3'be7ljWUUjieb0->%@.....", +"..*-_FryvEELvAxyzzzzsyBGLEEGx^c%.@=-~]^sBCCCCBAszrrzsBACCCCyzF;@+#;{/sGDELGAAyszzzsxBGLEELxK{~$+.%;]rsGLELvAtssrzzstAGLELGyI];*+.*!:rxGEEEvAxyzzzzsxBvEEEvyQ(-@..*;FKxDEEEGAxszzrzsxHvEEEGy^(-+..*)FztDEEEvAyszrrzsxHGEELGsI{,+....@&**39~(/rsttHGAJrF(!;,=*#@..+#-~]^sGLEEvvGGHBGCCBHvEEELyr_>.+@=-~]zyCBCBCtysszsxBCBCCCAsr(=+.&=-~]zyCCCCBAxszzsyACCCCCAs/;@.+*!{^yCCBCBAtsszsytBCCBCCyr])=+..&=;)FzBCBCCCtyszssyACBCCBxzF;+.....&=,;)F^stCCvEvHtz/{!-#@.....+#-;{/sxCHGDEDDDvDvvHAxsrF)!,@.....+#=-!)FrsAHvEvDGsrF);>*&.....", +"..#-'FrxHLLLvCCtxsssACCvLLLGsr;@+@*;)FryACBCCBtxzzzsytCCCCCAs^;@+#;]^stLLLGCCAysssACCvLEEDyr{;*+.%;{/sGvLLDCBAxssxABCGLLLvy^{;#.+*;FrJBLELDCCAxsssABCvLEEvs/),@.+*!]ryGLLLGCBAsssxACHvLLuHs/)=+..*!FIyGLLLGHAyszsstCCHLDuts/(-@.....+@*=-;)FzACGuLLAr]~;-$#&+....&>']rsGLEEELLvGCCACCBvLLLGy^)*+.&$-']rxBCCCCCAyssxACCCBCCBsr{>++&$;~]rxBBCCCAAyxsytBBCCCCAsr;&.+$;)^sACCCCCBxsssxCCBCCCAxr]'>+..%=;~FzxCCCBCBAsssAACCCCBCAs/;@....@*=;!{/ryACCCHHtyzF);>%@.....@=-!{/sAABCHCHtAyxysszrF])!,#+.....@*,;~]/zyBCCCHHtsr]~-=%+.....", +"..@*,;(}6OwwJJKI^[[QQKJJwwOP[_>+.+#,;)][rKJKKIr[F1]F2^KJJJKQF_-+.&=;_25ONwwJKKr[^QQKPwwwYJ[(;>&+.@$;_}5OwwwJKKQ[[^IKJJwwwP[_-$@..&=;(2PwwwOJKrI[[[IKPJwwO8[(;*+..&=;(}6OwwwJKrQ[[[IKJOwwO5}~-*...@$!([6wwwOJKI[[2[IKJwwwO5}~;#.......+&%*=-'1[PJwwO<|!-=#@.......+#-!([5wwNNNwwPQIIKKJwwww6[_-&..+&*=;)/IKzKKKrQ/[QrKJJJJKK[{;*..+&$-!{^IKKKzKr[/^QrKJJJJKI/{-+.+%=-~F^KKzKKKI/[QIKKJwJJK/:';%...@#=-~]QKzKKzKrQQ^QrKJJJJKr}_-.....@#*--'{F[QKPJKKI2(!-=%+......+#=-'{F^IKKJII[22::{(~!;-==%+......@#>-;)(FQKKPJKKQ1);,$&@......", +"..@#=-c|[P8OJ8P85555888OO654|;*..@#=;_1<5O6O8554:1|}[586OO8<}(,@++*=;([58888P855556666668<1!-$&..+*,!([P8PJP8K655566PO6O5<|;-#+..@#-0|[586O88P555586O8888[1;,%...@*,;|[PPJ6P8K5555668O886[1c>#+..@#-~1<588J885555556P6JP54(;=%+.......+&&$=;(:[88854(-=*@.........%=;_1<6OOwSSY655568PP6P5<:!$@...+&$=;(}[QIPPP5<556PPOYO8<[|!#...@#*-~|}[Q-!(|}<88J6J6<}_;>#&........%,;_(}[QP6P5Q}}:|_!;->=**@........&*=-~(:}<66OJ68Q:!-=%@.......", +"...&*,caf7hipoWWUUqTUqqjih7bd9#+.+$,cb7pqUTTUqqigbfgpqUUUUqjge-+++$=9abghimjqWUqoUqUqjmh7fd9,#@..+%=cab7ilpoqWqUqUUqqpli4fd9,#...+&=cae7hipoqWqUqUUqqjih4bd3=#...&*,3cbghinoWWWWqWWWqji77bac=%...+%,;df7hipqWqTTqTqWqpih7ba9=&.........+&*>3abe7i5fba9>#@........+#=9ab75mmoUUUWqWUqWqpmh7fd9$+...+&*>-adefipqqWqqqqWqWUUUjied*....&*>cabe7ijqqqqWqWqWWUUUjie9@..+#*,cdbegpqqWqqqqWqWUUTqjgbc*....+%$,;ab:gijqZqqqqWWWWUUqjge3+...+%*3abfgijUUUqWqifa3=#@.......+#=;de7hijqqoih7f|a93=*$%%@.......+%>3ae47ijqUqWqoie03$#@.......", +"...&*,9ab4klnZUTTTTTTTWolk4b09#..+*3dfljqTTTTTqjk77ijUTTTTTZm4c@.+&>-a1ekhXWUTTTTTTUUomh7fac>*@...&$-ab4hkXZWUTTTTTUWolk7eac=#+..+%>3ab4hloWTTTTTTTTWolh7ea9>&...@#=9a|4kVpZWUTTTTUUWolh7ba9=%...+%>9ae7hlnWUTTTTTTUWXVh4ba3*&..........@$=3~be77<7ba3**@........+#=!aekVVXoWTTTTTTTUWolh7:a9*.....@#=3ade7mZWUTTTTUUWUTTTWphb,+..+&$=9~be4mZWUTTUUUUUTTTTqm7a&..@%>,c!b4koWUUTTTUUUUTTTUjl7d>+...+%*=9adb7mZWUTTTTUUUTTTUqm4c@...&*-017lVmoUTTTUWogdc>*&+......@*,a|kVXXZUUZXVlkeac,>$&&+........%$,a|7lVnZUTUUUqpfa9=*@.......", +"...&=3!|^stBGDELEEEEEEEGAys/{;$+.&>'FsBvEEEuEEuDtxxBDuEEEuEEGx(#+@%>;(/stAGvEEEEEEEEEvAAszF~-#@...%>;(/zxtGvEEEEEEEEEGBxsr{'-$+...%-;{^zxAGEEEEEEEEEDvtysr]!-#...&=,~{^sxCGLEEEEEEEELvAxs^{!,%...+*3']rstBHDEETEEEEEEDAxs/{;-#.........+%=-!{/zxyxs^('-*@........@>;)FzxCCHGEEEEEEEEEEvHtsr1!$+....&=,;{FrstDEEEEEEEEEEEEEEDtr;@...&=-'{FrsHvEEEEEEEEEEEEEEGx{%..@#,;')FzxDEEEEEEEEEEEEEEEHs/-+...+%$,!{FrsGvEEEEEEEEEEEuEEAx)&..+%-!]zxCCHDEEEEEEGs2)3,*+......&-!]zxACCvEEvHCBtzF);-*&+........+*-~FztCHGEEEEEEvGKF~;>&.......", +"...+#-!{/zsAtGDuuuuuDvGHyxz/)-#..@-~FzxBDEuuuuuttssxGuuuEuuHAz)&.+#>-~]rsytGDEuuEuuvGBysz^{!-$+...%>;)FrzttGDvEuuuDvHHtyz/);-%@..+%=;)FrzytGvEuuuEuDvtysr/{;,%+..+#-;)/zsAAGuEuuEuvvGHyyz/);#@...+%-;)/zyAtGLEuuuuvuvAyszF);=&..........@$-;)]/zssrF);-#+........&,;)/zABCBGvEEuEuuEuvtxsz/];*.....+%>-;]/zyGGLvEELLDDEuuuvty/;@...+%$-']^zxGGEuEuEEvEEuuuGAx)&...+#>;~{^stGDvEEEELvEEuuDAts]-+.....&>;)]/KxGvvuEEuEDEEuuDGAs)&...#-!]zyACHDvEDDDvts/~;=#+......@>']ryCCCHvvGHCByr{!-=#+..........#-!FzAABHvEEELDDxz]~-*@.......", +"....+%*-;~(:}4[k$+.....+#=-!~):24<<5kh<7[42:)~;-=%@...........+%*-;')))~;>*#@.........+#,;(F[KKKP<<<<<<<<[[2:(~;-$+.......%$=-;~1}[<-;00_~__d___a';-,>*%+......+%%*--99aa~__a__a~!;--=*&+.......+#=,-;0aaa~_da_d_~;-,=*%@.....+@%#=--;0aa_0~__~_~!;-,=*@......+@%*$-3c~a~ad_d____';->=*&@.............+&%$>>,->$$%@...........&>;~12QQQ[}:|(_d(d_a~;-->$&+........&#*=-90_a________d__a!-$+......@&**=-90a_a_a__(_d~__~;;#+.....+&**>9c0a__d(a____d__';-$@.......+&#=,-90_a__d__d__d~_'!-#+...@%>-!:}[[QQ#+........", +"......+%**==->,3999033,-,>$*%@....+%$>,3,9c99993-3,3333333333=@.....@%#*$=-393399933,9-=**#@........+&%$>,,3339033333,,=$*%@........+&#*$,,3,939c09333==$$*@.......+@%#*$,-9-cc999333,==$$%+.......+&##*=,,-339c9333,,>>$#%@...............@&****#*%&+............%,;_bbf4fbbaaa0909933,=**&@.........+&#*$,,33,3333399c3933=&........+&%*$,,3333333909993,,=&......+@%$*=,,339cc99333cc93,*#+........+@%*>,>339999c9c3c093,#@....@&*-9def44fffffba0,$&..........@#>cdbe4777febbda-*%&............@#=-abef44f4ffeba;,*&+........", +".......@&##==->,,>-33>,=$*%%@......@&*>=>,3--,>=>=,=>>,,=>==**@......+%%**=,=,3-9,>,>==$*#@+.........+%#%$=,>9,3,,>,>,=$#%%+.........@%%**==,=9-9>-3,=>**%&+........+@%#**==,9-9,,,=,=>*%&@.........+@%$$*=>,=-39,,>>,$$&%@.................++&&%%&@+............+&*99addbddc0c933-9,>,*$#%&...........+&#***>,>,,,,,,-3,,>=$&.........+@&*=>=,,,,,3,39>>,>>*@........@&#$*>>3-3,-,,,39,,=$*@...........&%*=,=,--9-39,,3,3,=#+....+&*>9aadbbe|bbda03=*&..........&*=99dbbbbebddaa9>>#@+............&#=3!dbbbebddaac9**@.........", +".......+@&##$>---,,-,,,>=#&&+......+&#=,>,33>>==$==>3-,-3,,-$%........@&#%*>,,-,3--,,==*%&+...........@%%#*>>3,-3--,,>=#%@&...........&&##=>-,--3->,,==#&@@..........+&&%#=>>-,-3,,,,==#@+..........+&&#*>=>,3,,3--,,,=*%&+...................++@@@+..............&$,;!''_~~'!!;---,,>,=$#&+............+%#**$,,,-3--3-,,>,=*&..........+@##$>-33333-3-333>=#@........@&@#*=,-,-,3-3--,-,,=#+...........+&#>=>3,3-,9-3-,,>==%......+%>;;)~]{{(){);';,*&..........&$>;;))(]{{{();!;-$%+.............&=-;;'){({({~~!;-=%+.........", +".........++@%#**#$$*%#%#@@+..........@%%#$#*###%%&##%%$$**%%%+........+..+&%%#*$***%##%%+.................&&###%#**%%%&&++...............@&#%##*%%#%#%&+++.............+++@%%%#%%%%%%%&@+............@&@@@%$$*$$$**##%%&+........................+.................&*==-,-,->=>=>***%#%%&+...............+.+@%%##%%****#%##%&..............+@%%%%%*##*****%%@...........++&%%*$*****#*$%%%&@..............@&*#*$#$===**#%#%&+.......@#$>=-,,---,=-=$#@............&*$>,,->-;---->#%&@+..............@*=>-,---->-->$*%@..........", +".....................+.+.................+......+........+..................+.....+............................+..+........................+++....+.............................++++...........................+.....................................................+.+.+.++++.+.++.+................................++..........................++.....+.....................+...+...+.+.....................+..+++++..+............+++++@@+@+.+.+...............+++++@@++++..+++..................+..+++@@@+.+...............", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", +"......................................................+@%%%%@.......................................................................................................................................................................................................................................................................+&%%%%%%#%%%%#%%#%@+........................................................................................................................................................................", +"................................................+&%**=>-;;;--$%...........................................................................................................................................................................+@*****#%+...................@%***$$**$**$*#%@.................+@@+.....................+#>,;;;!;;;;;;!!;!;;->=*#&.......................................................................................................................+@&%%%%+.....................................", +"........................................+@@&%%%##*=--;!)))~~;-*+.........+@@&&@+@@&%%@+..................................................................................................................................................+%=>----=##@.................&#*=,---;;-;--->=*%@..........+&%%%#**#@....................%,;~))))))))))))))))'!;,=#@...........................................++@@@@++..+@@@+..........................................................@%#*=$>=$##@...................................", +"......................................++@&&&%#***$>,-;~)(()';-*+.......@@%&%%&&&&&%%%&@+++...........................++...............................................................................+++@@&&+...........................@#$,---,=**@+................@#$>,----------,>$%@..........@%%&#*$$#&@+..................%-;)(((()~~~_~)_~)((~!-=>%@.......................................+@@@&@&&%&@&@@@@&%@@@@+.....................................................+%#*$>>,==**@...................................", +".....................................+&%###$>*$$>=--!(17<7}:(;$+......&&##*#####***$$$#%&+........................++&@@@@+...................................................++&&@@+.................@&%#&%#&#&+........................@#$,-;~!;->#&................+&#=-;!!!!!!!!!;;-=#@..........@&#$*>,=*#&@+................+$;b}4<4}::::|:::2}4[7:_9=$@...................+@+................@%%#%###*#$#*#######%##%&+...................................................@%*>--3;;;-*%+..................................", +".....+++@@+......+@@@@+.............@%**$$=,333333;abeijqjpgfd-+.....+#*>>,>=*==,>>,==*$%&+..................+@@@@%&##$#$%&@+..............................................@&%%##%%&+..............+&***$*>$*$&@+......................+&$=3cbebdc,*#&+..............+%*,0dbbbbbbbbbddc-$&+.........&%$=3c99,>$%&+...............@-agjjqjihh777774hijqjjec-*&+.................+@&&&&++...........+*$***>=>,>,=>>==>>===$**%&+.................................................+#*=9;a0ddac>*@..................................", +"....@&#*%%#%@@@&%%%####&...........&=>,339caada_addb4hjWWWoPl49&....+#=,9cc0999c90c00c9,**&..........++@@&&&&#*$=>=,33,3,>**&+......+@@&&&&&@+.++@@@@@@@@+...............+&*==,,>==$#%++..........+%$,39399393>*&+....................+%$=3014k7edc-=#&@............+#=3cd4444444744eedc=#&........+#>3cadd~a-=>*&+..............#9biqWWZoVVVVVVVVVoZWWqgb9,*@...............+%*$=****&@.........+#=3-ccc0c0c0cc9c0c00;c93,>*&................................................&*=30dbbeeeed;,#@.............+@#%&&+.............", +"..+#=---;--,=$*==---;;->%@........&>!){]]F/rzzrrrrrsxBGLELGHAs)#...&*-;~]/^^F]F]///^//]);-=%+......+#*$>-,,--;;!~~)){{]{{)!;-*+....&*$>,--,-,>=>>---->->,$%@............+%>;!){{{{)'-,$$@+........#-!){]FF/FF{~;,*&+................+&$-;!)/zABBxrF)';-=%@..........%>;~FzAABAABBCBBys/{'-*@......+#-;'{/rzz^{~;->#&@+...........-]KALEELvCBCCBCCCHGLEELxQ(;-#+............+&$>;;!''!;-=%+.......&,~{]F/^/^^^^/^/^^^^///]{)!;*+........+@%**$*$$*$$**#%@+....................%>-!{FrssyAtsr])-*+.........@%*>>;---=%+...........", +"..%>-;')~~';--,,-;~))~!--*&+......#;{F/rrzzyytyzzssyBCvLLLvHBz)%..&=-;~]rsysrr/rrrzzzzrF);>*&.....+#=-;-;;;!'~)){]F//rrr/F{);=%...&=--;!!!;;;;!!;;;!;!;;---$#&........+##-;~]/rr//F{!;-=*#@......+=!{F/zsxxssQ]~->*@...............&$--;!)]/xACCAsrF)!--=##+.......&$-;)/yACCBABCBCBAsr]!-=#+.....@>;!)]rsyyz/])!-,$*#%%@@+.....+,FztDLLLHCCCCCCBCCGLLLGyI]~-%.............&*-;!){{))~;-*&+......$~FrsssxyxyxyyAyyxxyyxJsrF{'=+.......+&*,---;-;;;;--=$*@+.........+@@@.....%$-!)FrxtCCCCAs/{!>+........&*=-;'~)~;-=#&..........", +"..%==;!~~);-=>*=-;~)();-=*%@......$;(]}/[/IPJJII[//QKKOwNwwKQF;&..@*=-;([PPI[FF2/[/Q^QF(;,*$&.....+#=---;;;;'~))){1F:/[F}F(~-=#...@*=-;;;;;;;;;;;;!!!!;;-->*%&+.......@#*=-':#@..............+#*==--;~:IJwwKQ|);;-,=**&+......+%$,;1IJJKKrKzzr//F]'-**&+.....@$=-!)}<66<:(!;,>$*#&&&&@+.....>~}PwwNwJJKKIIrKKKwwNNw<2~->&............+&*>-~({{{)~;,*%+......#_[K6OJOJOJOOwOOOJOOJOO6<}|!$+......+@%$>>>-----;--,=*#&&+........@&&&+...+%*>-'([PJwJwwPQ:)-*........+&#$>-'~~!;,*#&+.........", +"..#=-;_(1(';>=>,-;)1:(~;-*%@.....+>_12[[QQ<6YY8<[[[QQPOwwYP<2(=+..@$,-~|5665[:::}Q[QQQ}|!->*%+....%=-3;'~~~_~((1::}[QQQQQ[1_;-&...&$>3'_(_~~~~~~~~_____!;;-=>#@......+%*=-;(48OY6<}(~;,=**#%&....+=!|[5OYMSSR5:c-**@..............&*=,-;!~(}**&@.......+&%%%&@+@&%$=;~|[6YRSSO6Q|'-%+.......+%$=-!~(((;->*&@.........", +"..*30df4gebc99-9!df7gedc-=$&.....+>b47hhimpjqjmi74hhiXpjopm7e0=+..@=3cdeijqp7ff47hhhkh4ea;,=*@....%3abefeegffgggggiiipipiigb0;%..+&>0affgefffegeeffegefebbc3,*&....+@%**,9abgpjopi7bdc;9-,>*%@+..@,0b7ioqqUqqjfa9>*&..............#,90ad_begiqUUji7bbbdaa;3>*&.....@#*9ceiqqphkhk,>==$&@...$cfiqTTUjmilimmmmpooWqjgba9=#+...........@&$-0be47fedc-$&@......*agipjjojqqqqqWWqqqjqjjjigbc*+....+@%$>3;adbbeefebbac9,>#&+......@$,==#%%&%#*,0dfgjWUUWjpgd0>%+.......@#*,;dbfeed;-=#&.........", +".&,a17ipjmged~adbfipnhedc3*%+....&c:klXnZoooojmhhhlmoonXmm7fba>+..#,0de7pZZnhhQhlVlVVlI7edc9=%+...>bgpjjjqjjqjooooooZZWWqqmhfd=+.+=cbgppjjjjjjqjjjqjqjjjig4bc,%+..+%*=3;cade7mXnmlkk4eb(daac,*@..@>ab7lmnooopi7ba3>*&............@,a:e47kkkloUTTWphhkkk4e|a9>%+...&*=3cd7mqZXlllVVVlk4edc3>*&.....#,cdb4ijWqpheeba'aaaaaaa09>*@.+=afjqTTTqXXXoooooZooXomhed03*@...........@$=cdekllk2ba9=$@......%3dfggghiiiipoooopimiiiggba0$.....@%>3cd:ekhijjqjjg7e|d9-*&.....+>adac-****$,3~e7mZUTUjpgfd9-%........&*,cbe7imigba9=&.........", +".$~rsBGvDvAsrrrrzsHDDys/{~3$&....%(rtBGvLELvGHCBAACDLvGHtxsr/)-+..=)/zsxGLEDBttAACCCCCCtxrF);,#..+;VwEuuETuuuEDDLLvLEDuuEEDAxr;@+&;{IxvDEEEEEuuEuuuuEUuuuGAz]~$+..%>;~)]/rrsxBCCBCCAyyzzzrr]);*+.%;{^stCBCBHCBxKF(!-=&...........=]rsABBBBHHvEuEEGHBCBBABsr]~>@..+*;!)F^sHDDGBBABCBBtys/])'-$&...+,!]/zytGLLHtszr^//^rrrrr/]'-%++;2suuuuEDHGDvEEELEGGHGCyz^{'-%..........+#-;{/sBBAtyzF~;,%+.....#9_b}^rKsssyHCHHHAAsssKzQ2]~,+...@$-;)FzstAHDEuuuDvBtyr]~;,#@...&!rz/]);;-3;!)^stGEEEEGys^]~3%+......@*-']/yAHDDtz/);$@........", +"+={rxHDLLLtJrrzzsyHGvAJ/{';=@....%!zABHLLLLGCCCBBBCGLLHAAsz^]'=@.+>(^zsAGLLGtyABCABCCAACyz/{!-*+.@~zGDEEEEEEEEEEEEEELEEEuLvHy^;@.#-]zAuEEuEuuEEEEEEEEEEEEGtz/'$+.&=;~)F/rzzsyBCCCCCCByssszr/);*+.&-)/zyACCCCCByr/]'-=#@.........+-]zyABCBCCCLEEEEGCCCBCCBxz/)>%..%,!))FrsGDLGBABCCCABAsr/)~;;*%..%,!]rzyBvLLCAyszzrzzzsszzrF~-#+&-^svEEEEvGCvLLLEELLCBBCts/F);%..........@*=;)/stBCBxrF);$%......&-;))F/^rzsytCBCCAyszKr/F])!>+...#,-')/sABCHvLEEEuDHByz/{';=#+..&~^sr/{!;;;;']ryBHvvELHsKF);,%......&*,;~]rxBvLvAs/{;>&........", +".*;1IPJwwOP[:F1F2QPwOK1);-=%+....+-(/IJwNwNJKrQ^/QIOwwPI^2F(;-*+..>~F}[KOwYOIQIrKKKKKKKK/}]~;,%..+-15OYRNSNNNNwNNwNNNSNSNS6I})>+.&=':8wNNNNSNNSSRNNNNNuSMPQ2)-%..@=;'(|:}}2[IKKPKKKKI^QQQ/}{!-%..@=;_]^IKKJKKI[]{~;-*%+..........=):/rKzKzKJONNNwJPKKKJzI/F);=%..&,;!_{}QPOwPIQIKKKr^^/F{~;--=&..@=-~]2[IOww8I[[[222[Q[[/[1)-=&.+,{[6SNNNwJJwwNwNwwwwwJJI[:(;-&..........+%#,;_//Kr^F{)->#+......+$>-;;!)_|F2QKJJKQ2}]]())';-#....%=-;_]/^IzJwwNSNROKIQ}{);-*#+..+-]F}]~;-->--!|/IPwNwO5[(!-=#+......+&*=;_FQPJwO5}_-=%@........", +"+*_25PJOJ8<42:::2[6O6<(0-=#@......$~:[5OSNY6<}}:}}<6RY6Q[}1_;=&..+>(4<56YMSY855586JO86885<}_;-*+..*_:<<5558RRNSOOOwwNSO855[:(9*++@,015MNNMO88556558pMSNNM<}(~-#+.&>;(:4<<<5558OO8886656555<1_-&..+$-'|[58O8J65Q}1(!-$$+.........+$'1[QQQIIIPJMNNR6II6JJPK[:_;=%+.*;_(1:}[6YY6(45MNNROPIPOOOOOOJOOYJ5<4:~-%+.........+&*>;_1[Q[2:(;-*%+......+#*=-,;!_(|}[IKIQ}11((((__!-*+..+%=-!(}[QI#+..@-(24[2d;->,-!(1[<6OO8<|!3=*@.......@%$,;(}5P8654_9-$%+........", +".=dijjXmmlh774474ijqjgd9=$%@......$0begoqWqmfeb|bfgjqqpi74ea;=%...3fpjqqUTTUqWqqZqWqWUWqojgfac*+..%,cadbffgpqqon6mnqqjigffba9,#+.@3afiqWqjig7ffffffgjqUqjgba;,&..%3agijqqqqqqTUTUUUqUqUqWqjib9%..+$3abijqUWWqomh7fb;=*@+.........*d47hklllhmjWUUqpmmoWqqpifb;=%.+>0ee77hiqqWpimpoqjmmk5h74bbd;*+.@>0b4hhmoqUUqUqUqUqUqqqWjje0=&.+3egpqUUWjmmlhilllmoqUUWqqjgb0*+..........@*>;de77771d03*&.......+#,9c0aaabef7kl5h4e1bbbbbbb0,@..@$-abf4hhkhlmmmmiipoqopgf_0,*@..+,bgijiga;--9ade7glli5fd;,*#@.......@#$,;bijjmigba9>*%+........", +"+$biqZnVVVVlkkhhhVoZoibc,*%+......*014loZWZm7eeeefioWWnmVh7ba3*+.@9gpWWUUTTTUWWWWUWUTTUWWomfb0>+..%=990dbfgmoZXXVXnZZXifbdac-=%..%cbgjWUWohk4eeebffgpqTWjied0>#..%cbhpZWWUUTTTTTTTUUTTTTUWZpe0$+.@=cb7pqUUUUZZXlVbkVVVVVlVXoWUUWoXnZWUWoVh:a,&.+,:7kllVXoUUonXoWWZnnXVVVhke1d=++#3d4hVXXZZWUUUTTTTTTTUUWWjgc,&+@9elnWUUWZnXVVVVVXXoWUUWWWoif0$..........+%*30b4hVlkeb_9=%+......%90_db1be47klVXmVhk4444k[74(9#..&3a:7klVVVVVXXXVlhmoWZomkfbc=@+.@3ekpWoibd00~de4kllVVl7b0,>#@......+&*=3cfpqZnl7109,*&@........", +"+=eJtHCCCCCCCHHGGvvGHx^(;-*@......>{ryAGGvGtszrrrsxvuEDvGHtz/)-@.@;KtGvLEEEEDLvDDGGDDEDDDHyrF{;@..#>c'_1/IsAvGvGCHGHvAsr/](!3,&.+$)/sHEEEvHyszr^^rzsvLELvtz/{;*++$_^sAHvLvEEEEEEEEDEEDEvLvDtz:,+.&;(/sAHLDELvDHCCBzF);=%........+;^AACCCCCCHvEEELGHGDEEEDHtsF'=@&;ztBCCCGvEEvGHGEEvGGHHCBAAys^;@+>(rsBCCHBGLGDvEEEEEEDDvDGAIbc%+&~sBHDLDLDvGHHBCCBHGDvvvDDHtr{-+.........@$-!{rxBCBtsrF)-=@.....+-]rzsxsyABCBHvvvGHAtBBBBBAxr)$.+=)/ytHCCACCBHCBCBAHGDvvGGAzF;#+.@'^xGLvHsr/F/zsACHCCCAs^(;-*@......@#>-'FVADDGts/(!;,=%@.......", +".=)FrsxtBCCCBHvLLGGCxz/';=#+.....+-{zxCCCCByzrrrrzyvuEELLGAsr{-+.+-]zABHvEELHCCCBCCCCCCCxsr:);=+..&=,-!)]/rsACCCCCCByzrF(~!--#@..#~/sHuLLvAysr^]FFrsGLLLvys/(;$+.%;]rsyBCCCHvEEEEEHAACCCHCBtr(-@+&-{/zxBCCCCCCBCCtz/~->%+.......@;rtCCBCCABAGLLLLBAAvLELvBAs/)*+@'rtCACBCvLLGBHvLLvCCCCBCCBAs^;%@,]rxBCBCCCCCCCCGLELGtxysz/F~-#+&~rACBCCvvLvHBCCCCCBCCCCCCBs/~*..........%=-~]ryABCAsrF';=&.....@;/sBBBAABACCGLEELGCCCACCCByr)$++>{rxDGHABCCCCCBAyyACBCHGvHJF;#+.@;/sGvLtxzr^rztGLGHCCAs/);-%+......+#=-!FstLLAyzF{~;->*+.......", +".&>-'(]2^rKJJwwNwwJQ[:~->#&.......$'2^rIKII/1((((1<8wNNwNw6[1'=@..*;]2[IJwwPI//IrKzKKKI/F{~;,=%+...+&*=,;!|}QKKsKKr[](~--==*%+...+>~:5wNwJI[](';;'([6wwwJQ:~->&..+*-~(2[^IKKJNNNRPQ[QIIIKKI[{'$+.@,;(][QIKKKKJsKKQF)-,*@.........-1/IrIrI/^QJwNNOPQKONNwJKQ2);%.+-|^rrrKPOwwJJJwwwwJJsKKKrQ/F)-@+$']/KKKzIQ^Q^^IPOwO<[::|_';>*+.+-1^IKKJJwwwJKKzKKKKKKKKKQ[F~,&..........+%>-!{/^Kr/F{~->&+.....+-]^rrIrrKKzJwwNNwJKKzKrKrr^];*+.*;:5OwPIQrKKI^Q/[/^QIKPwwJ[_-%...>!26OOPQ21||}*$%@+......", +".+&=-;~1:2...#c_|}46MM522}}QQIQQ[[:(!3=*&+.....+&**-;~|}[QQII[1_;->*#%&.....+*0:5MSR6[:(!---'_:@+*!(2QIIQQ}}}}}[&..+%-(<8O52:(((1<8YOIQ21_-=*@+........@*>-~:5MO**#&+.....", +"..@$>9abbf4hmoWUWqmgba3=$#@.......*cb477kk7edaaadbgpqWWWqjmgba>+..#3abbgpqqjgff7hhhhhk7ed9,=*&+......+%$,3ab4khlhh4:a-,**%@.......=aepqTqjgfd99-9c0fiqWqpgbc3=&...&*3cab:f7hmqqWji7f7khhhhkfac#+.&>cde47khkmppmhked;,*#@.........=b7khkkk74gpqTUqiggjWWWpi7ba3&..*cbe777kpqqnimponmmhhh77eeb_c$++*0bf7hk74eeef47ijpigbac;>$$%@...=ae4kllmoWWopmmmimmmmihh7ba9#+..........@&$,cd7kih7:dc,*#+......=dbef44777k5pqWWjmh774444e|a-%++>cfijpi47kllh7ebbb:e4hijqpga>@...&,0gipgfebbbeijqji7f10-=*@........+#$,9dfiqjieb_ac;3->>*%@....", +"..@*=3ad:4kVnoWUUWphedc3=#@.......=d4klllhke1dddbehjZWZXonXh4b,+..%9a|fhjqUjhh7kllVlllk4dc9$$&.......+#*30(e7kVVVlkba3=$%+.......@,dgpWTWohfb_c99abfpqWZog:ac,%...@*,cade4klmXZoni4khlVlVlh4b9$.+%cb4khVlVXnZomh7edc,$#@........+,1kllVllllVoWTUqpmmqUUWomk4d9#.+%cdb44kioWZXVVXnnXVlkk4ebdac3#++,b47[77k447khlhVnXm4ba0,=#&@...+>deklVXnWUUWWqqqZqooooXi7b~,=@..........@$=3afinoni7ba3>*@.....+*cad|beee[7hmXoonlh74eebbdac=@.@,b7lnnVlhmnom44e:ee7khXZWjgd9#...%>cbggefee4kkmoZol7eac,*&+.......+%*=9dekmopiffe4e|d_a9,>#@...", +"..&>-'{F^stCHvEEEuvyz/{~;,$@.....+;rxBCBBCAszrrrzsyHLLvvCCCAy^;&..$~FrzyDuEvAtBBCCCCCCtz/);-=&.......@=-'{/stCCCCAx^{!-=&........&'QsvEEuvAsz^{)(]^JtLLEvtz/(;*+..&,-')]rstCCCGGGAttBBCBCCAs/)>++=]zABCBCCGDDGCyzrF);-=%&@......+;zAACCCCCCGDuEEEGHHEEEELCAs/)=.+%!{/rzsyvEDDHACCCCCBAxz^]{~!-*+@'ryyssssssxtCBCCHAtz/{~3=*&+....-(rsACCGEELEuEEEEDEEEvDtz/{'-%+.........%>-~FKtvuvHsr]~-=%+.....*-~({1F//zsxBCGGBtsr^/F]{)!;=&.@!rACHCCACvDDAxzzrrsABHvvEvxr(>+..%,0(2^^zzsABBHLDGxzF)!;=#+......+&=-;{^stCHAAssyBtysr/F)~->%+.", +"..%=,;)FrzACGvLEEEGxzrF);->#@....+;^yACBBAAxzrrzsstGvGHBCBBAsr;@.+*'F^zxvuDGBAAABACACAAz]';,$&.......&>-!{/sAACAAAs/_;-$+........&!/xGuELGAyz/]))F^zALELHAK/);*...@*-;~]/sBACCCCGCAACCCHBCyzF;*++={sABCCHHGGGGtsz/]);--=*%&&+...@;ryCCCCCCAHGEEEEvBHvLLGvByz/!*+.#-']/zsxvGLHACBCBCCAysrF)';;,&.@;^zzrrrzzssBCCCCBByr]~;,$&+....+=)^sBCCHGDDuEvuDuEuELvvAK]~;,&..........%>;)/sGLLvtJr]~-#@......@,!'~~){F/rsxCCCCyz^F))'!;>=#+.&~zxACBCCBHGHttzrrrytCCCvvGy[~*...+$;')F/rzxACCGHGAJ/]';,$@.......+*>-'{rsBCBByxstBBByyzr/)!-$@.", +"..@#*-;'{}IJwwNNNw6I[2(!;-=*%+....-(/IrrIr^/]|F2/[IPPKI[/F]])!$+..%-~(148OwPIQ/IrzKrKr^])-=$&+.......@*=-!)}rrK^^F]'->#@.........+>_[8wNwOI[:)';!_([PwwwOQ:)-=&...+&$,-!{/rKKsKJKQQIJJwwPIF{!>%+.#!FIKJwwwwwJPQ2F(~;,=>$#%&&++..+>(/^IrQrQQIJNNNYPIIIJJJKI2{!-#..@=-'(F}QPwOPIQrKIzQ^F]);-,=#&+.+*;~)~'){]F[PJwJJI[]_;,*%@+......&,)FQKKKKPP555I5666OJJP[{!-=#+..........%*-;([6wwwP[(;-*&+.......#=>==--!_(F^KKKr[])!--==#&@...@-:^KKKKIKKPP<2|||]FQrKKJPI2(,%+...%*=-')(2IPJwJP5[1~;-=%+........@#$=;)]QrIrQ[F2/^KKPPIQ1~;,*@.", +"..@*$,;_(2<8SNNNSSO>&+...+*~:[Q[[[2:|(|1}[[QQ[:|_!;--=%+..&>;~125YY522}}[QQQQ[}(;->*@.......+%$>-;(2[Q[}11'-=#%@.........+>~:5MSM6[}|_!!~_(}5ORY6}|0->&...+%=>-;(:[III%..%;_[IJOwwJPIQ}:__'3->=*$$##%&+..*!(12[[[2}[5YNSO<}2Q**&+.....&31}QIIQQQQIQ}((~~(}[QQQII[:0,@....@#>,;;(:*&+.........@*=-;(}[QQ[}}(|12[6OYO5:_c,*+.", +"..@$=-cabeiqWTTTUqolh4bbac9>#&...+$~b747hk7f:bef774747bdc-,,=*&+..&=9abeiqqjg7fe7khhh7fb93*#+.......@%*>;ab4k<7:b_c-=#&+.........+$0fpqUWjg7bdaaadbfiqWqpgba3=@...@#=,;abeklhhkhh77gpqWqpgba9=@..#cbhmnWWWqnmh4ebda09--,-=*$#%+..#9dbeeeeebfiqWUjiff7hlh4eb03*@..@*3cbf4gpqjiff7hh74ebd9-$*&......%$>=-;adbgjqWqjfda3=*%+........+*cb7khk74ffeeff77hhlhkfa3=*@..........@*=9c_gpqWqif03=*@........+&%##*>-cab4hhl7:a3,*$%@......%9b4khhkkhhh7fbdadbf7hkhhh7b0=&....@%*3;abeiqWqpgba;,$*&..........@$3;dbekk44eebb:fhpqWqigdc-=&+", +"..#=30(e4hpqUUTUUWoXVhk4e1d'-*...+=aeklVllhk747klklll7e_9,,$$#@...&,0de7pWqoikkk*&......+%=3ca|4kllk4bd03>*&+.........@3dgjWTWoVh44eee}4hpqWWohbac=&..+%>9ad|eklVVVVVllhVoZWZnkb'3=&..*a4hXnZWWZZXlh74feebb_aa'0c-=&..#9ab:ee4e47pWUUqih7hlVlked!3*&..&3014khlnqomh4hllVkkeb03=#@+.....&*=>90d:7hjWWWpgba3$*&+........+*cd4khhk774447khkVVVVl4dc,$&.........%*=,;aehpWUqpbc3$$@........+@@%*>,ca14kVVVledc,>#&+......%ceklVXVVVVVl4ebb1e7lVVVVV7b0>&....&$3;abe7pZWZnhe_9,=#+..........*,0_b4hVllhk7[47hmoWWWohfd0-%.", +".@>!)/sAAHGvLELDvLDvvGGGAts^{-@..@-{rytCHGHCAACCCCCAAsr]!c->=*@...*;{/zxGEEvHBBACCCCCBtz/);-%......#>;)/rsyBCCxz/F)!-#@..........&'/JHuEEvHAAAtxAtAAGEEEDxK/)-#+.@=)]rzsxBBCCCCCCHHvGvLvGs^{!-%.+>{zACHLDvvvGHHHHHBBtysszzr/]'$+.%;{F//rzzzxGEEEuGxxtAAxs^F);>#++>)/sAHHGGvGtxxBCCCBys^{;-=%......#>-;'{/sxCHDLLts^{;,=%.........@,~FrsyyxxyAttBACHCHCCts/);-&.......@%>-;)FrsAGDDvAK]~3=&+........&%=,;']rsyBCCBts/);-*@.......#~rsAHGGGGCAtxszzzytHHGHBAs/)-#...@$;~]^zytGGDDHs/{~c=&..........+-{FzsABHHHCCBABAGDDuEEvHtz/)=+", +".#;)FzyDDGGHCBBtBHGLLLEuDGAs/;&.+&-)/zAGvGvGCCCACACByzF);->=*@+...*;{/zsHuDvHCCCCCCCBBys/);=&.....+*-)FzyAACAByr/]'-*+...........%!/sHDELLHCCCBCACCCvLELHAKF);#.+%;{/stBCCCCCCACCGLLDHCCxzF);$&++=]rsxCCCCCCCHGDLLLLLCCABAxsr{>++&>;'){]//zstuLEvtsszzzr/]'!-=@.+={zxHLGvGHAysstCBBBxzr)'-*&+.....%,-;~]rsBBCCBAsr]';>#@.........@>!)]/rzzsAABBCCvvvBCBAz/)-=@......@#>-;~{/sBCCHHHyr]'->&.........@%>-')/ztBCCCCAs/);=%+.......@;/sALLGLGHByszzzsAHvLGGAsr]'-&...%,;)FzxACCCCAyzF);-#+..........@;FzxtCGGvGBCBCCGLEEEEuLvByr),+", +".%-~_[6OwwPKQ[2F2QIwwNNNwOKQ{-@..+*-~1QPOwwJJzKKzKr/])!-=$#&+.....&=;~|[8MwOKIrKKzKKrI/F~->%+......#-'(/QrzIr^F)!-=$@.............-([PMwwJPKKKKzKzKPJwwwJ[|~-*@..&-'|/rzKzIrr^/QIJwYwPKI[1!-=#+..&;~F[rKKJzJKJwwNwwwwJKKIrI/]'*...&*=,--!_126wwwO<21_)~;;-=>#@...$'2;'{}^KKKIIQ:(;,=#+...........+-(2^KJJwwJJKzKJJwNNNSNwJKQ1;$+", +"+*;b4<5665<}:((_(|4558O685<:~=@...%=;~|<586KIIQQQ[2:('-*#%@+......&*-!(}5YY6IQQQIIQQ[2}(;-*&+.....@*;~|}[Q[[21(;->#@+............+$;1<6OOPIQ[2[22[[Q5J88<}_0-*@.+#;(:2QQQ[}}::::[5885Q22(~-=*@+..&>~|}[[Q[Q[Q<5688OOPPIQQ[[}|;*+..@%#$$=,-!1>>*#&++..+>~1[566P[}((_(2Q[2}|(!-*#@.......%*,;_(1}2[}|(~;->*&+............+&#==-;!(|}QQIORS6Q[}1_3=%@.......&#>-;_(}-;):[QIIIQ[|'-*#@.........%-~:<5665Q2:}[4[<<<5P<[|~3,*&....#3'14[$#&............@-_2Q5POJJKIIIIIPP6OMMMO6P[|;*.", +"+>bgppig74bbdaaccadfegiiig7ba>@...&*390be777hk444e:(93=#%&+.......@*3cbeiqZpmhhhlhhk74:a;=*&......&,abe7hh44e(03=$&@+.............*9bglmlh7eb|b|b1be4hih7ba;=%@.+*0e77k74eb(dd_bf7gg7fbda9>$#+...@3ae747f4eeeffhghillllhh44ed;*...++&%#*>3cbijUWqgb09>$$*%@+.....,b7ghhg7fd000de474:ba;,*%@+......&=;abbee4e|ac-->$%+..............+&*$>-9_bf%&...........+&#=9cdeklllhh4bc,*$@.........&=3abbffefbfgjjqpi7febdc3-=%.....>0bgppm44ebd~;=$*&@............+,d7ioonmmlhhhkh77ghi5ipojifc*.", +".,fpqoik4ebac9999ccabf4Q4[f:a,...+&#>30d:f4h#%+..............*0bklVVh7e|bddadbb4khk4ba9,$+..=bkhlllk4|ddaadbe774eb(09=*&+...%317kk44eebbee477klVVlVll7edc%....+@@%>=90fiWWUjgb03,$*%+......@3ellhk74ed0c00b774ebd03*&+.......%,0|447k4ed03,=*%++...............+&#*-9abklVXoWWjheba9,$%@+.....@*>3de4lmoZoied09>*#@...........+&$,9'b4llVVVVkbc3>%+.........@*,90abeebe7pZWWom7ebac9,*#@+...@,b7pqoml7:d09,>*%%+............@,bgjWWoXVVVlllk747[hhlnoZpf0*+", +"@;IwDDAsz/F)';;;;'~1/zsyxszr]-+...@$,;~F^zsyBAtysz/]'-=#&+........&;~F^sGLLGCCCCCCBCtxzF)3$@......={rstBBBts/F~;-*&+..............>)^sACCtsr/F]{){]^zyyys^F~9=@.+-ryBAAAsz/F]({{Fzssz^F]);-=%....%~/xAyszrr^/F/zssxAACCCBAxz/_=+....++#>-'{IyDEEux[_93=#+.......@'sBCBxys^]'!~{rsxsz/]~;=#+.......*;{rsyyxsz/)!-,*&+.................@#$;!{/sBBHGEDHxz^]~;>*&......%>;)/stCHDDDyrF)!-=#@...........+%=-~{^sBCCCCBs^)'-#+.........@=-;'_//r/rJtDLEvts^](!93=%@....@'rxHDLGtsr])!-,=*&.............@!^xHEvGHCCBBCCtysssxtAGvEtI|=+", +"+;rtHAxKr/{!;;---;'~{/rzzrr/{-@...@%=-~{//rssyszz/F);-$+..........@>']^sAGGHBBCBACBAyzr]'-$+.....+=)/zyAAAyzF{!-$#@..............+$;FzAAttr^{{'!!~)]^zssr/{!,#@.+-Fsxyyyzr{'~'~~]FrzrF(~;-$&+....@;/zxszr/F](]F^rzyABAAAttzrF~=+......+$-;)/yGGGxzF)-,#++.......@'ryAxszrF)!;!)/zsz/])!-$+.......+*;{Fzsssz/{';,*%@...................@$-;~FzABCCGGts/F)'-#%+.....+#=;)FzyACHGtJ/{!,>#+............+&=-')/stBBABAs/);=%+.........+&*>;!{FFFFryHDGttI])!;,#@+.....&'^sAGGAyzF);--=*@..............&;/stvvHAABBBtxyzr^zzyytGtw^)=+", +"+$ae2e1_!;-=*#%#***=-;!~~~!;-%.....+&*,-;;')))))~';,#%+............#,;~1[;))){])~;-*#@+................&=;)]]])~!--=>>,--!)()';-*&+...#-)){{)~;-,,=,,-;'!;;->*#+.......$;~))~!;;---;!')){{F]]]{)~;>@........@#=-~:[<<410-*#@..........$!]])))!;>=>,-;))~;--=*&+........+$-!~)))~;-=*&+......................@%*,;)]F[[[[e1';-=#@+.......@%=-']F^[Q[e(;-=#&................&*>-!(]]F]]{);-$%@............+&*=-;;;;_:}[7}:~;-=*&+.......+-)2[Q4}(~;-=**&+................=~(e4[[}2FF]]])~~!~))1224:~,%.", +".&>;;c3,,$$#%+.++@&&**>,=>=*#@......+@&*==------,>>#@..............&*=-'(:::(__'_~!;--,*%@........+*==-----,>*%+..................+#>--;--,==**%*#**=,>>>=%&+....+*,-----,=*###**=>==*##&+........&=,---,===*$>>,,--;;;;--,,=%+........+&*=-0_((a9=*#&+..........+$-----,*$#%#$=>>>=*%%@+.........+@#,-->>->**@+........................@#$=-;!!_~ac3,$#%@.........@%$-;~(||(c99,*#+..................@*=,-;;;;;--=*%&...............&%**>>,-9cc009,$#&@+........+=_(__ac3,=**&+.................+#=9'__a!'!;;;-;--,-,--;0;3>>%+", +".+#*==**$#%&+......+@&**$**%&.........+&&*$*******%@...............@&*>9cdba9333333,==$#%@.........@%$$$>=*$$%@....................&*==,=>>*#@@@&&%#*$*##&&@+.....@#$=$*$$%%&&&&&####&@+..........@&**$**##%##*#*$**==,>>$$**@..........@&$>9c009=*$%@............#**=*$*%%@@&&%****%%&+............&**$*$$%%@..........................+&%*>,,3933=>$#%@..........+@#=-0adac3->*%@+..................+&%$==,-,,=**#@+...............++&%%#**=,-3,=**%&+..........=0a033,==*%&....................@#=,9,333-,,,=>$$*$*=>,===$*@.", +"..&*#$**%&&+........+@&&@&@+............+@%#****#%@+...............+&#*,99c993,>,===*#&&&@.........@%%*#*$*%@@+....................+&%##$##&@+..++@@&@&&@@+.......+&##*#&&&+@++..+++.++............&%%%&&&@&@@@@&&#*******%&&...........+%#=>33,>=$%&+............&**#*&@@....+@@&&@&@+.............+&###&@@++............................&#**====>**%%%@..........+@&*,90cc3==*%@+....................+@&#*******#&@...................+&&%##$*==**&@...........+%33,,==$#&@.....................+%*=====$=$=*$**#$%##**>*$*&+.", +"..+&%%%#&@+............+.+...............+@&%#%&&@+.................+&**=>>>=$*****##&&&&...........+&&&%#%%+......................+@&%&&&&+..........++@@+.........@&&&&&++.......................@@&@++.......+@%&#**%&&&@+...........+&&&%***$%#&@+............&&&&+........+++..+.................@@@+++..............................@&%#****%%#&@@+............+%*>>=>$**@+......................+@&&%###*%##@+....................+@&&%#%**%%+.............&$$$$**%@+.......................&#***#%%*#*%&%&&@@%&%**%#&@..", +"....+++................................................................++@@++++.+.++......................+.............................+..........................................................................+.+++....................++++.+..........................................................................................+.++++++...................+.+@+++++................................++.............................++++................++....+...........................+++.++.+.++++.+..++++++...."}; diff --git a/clients/multi-resource.c b/clients/multi-resource.c new file mode 100644 index 00000000..c4a0c18c --- /dev/null +++ b/clients/multi-resource.c @@ -0,0 +1,600 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010, 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "../shared/os-compatibility.h" + +struct device { + enum { KEYBOARD, POINTER } type; + + int start_time; + int end_time; + struct wl_list link; + + union { + struct wl_keyboard *keyboard; + struct wl_pointer *pointer; + } p; +}; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct wl_seat *seat; + struct wl_shm *shm; + uint32_t formats; + struct wl_list devices; +}; + +struct window { + struct display *display; + int width, height; + struct wl_surface *surface; + struct wl_shell_surface *shell_surface; +}; + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + wl_buffer_destroy(buffer); +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static inline void * +xzalloc(size_t s) +{ + void *p; + + p = calloc(1, s); + if (p == NULL) { + fprintf(stderr, "%s: out of memory\n", program_invocation_short_name); + exit(EXIT_FAILURE); + } + + return p; +} + +static int +attach_buffer(struct window *window, int width, int height) +{ + struct wl_shm_pool *pool; + struct wl_buffer *buffer; + int fd, size, stride; + + stride = width * 4; + size = stride * height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %m\n", + size); + return -1; + } + + pool = wl_shm_create_pool(window->display->shm, fd, size); + buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, + stride, + WL_SHM_FORMAT_XRGB8888); + wl_surface_attach(window->surface, buffer, 0, 0); + wl_buffer_add_listener(buffer, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + return 0; +} + +static void +handle_ping(void *data, struct wl_shell_surface *shell_surface, + uint32_t serial) +{ + wl_shell_surface_pong(shell_surface, serial); +} + +static void +handle_configure(void *data, struct wl_shell_surface *shell_surface, + uint32_t edges, int32_t width, int32_t height) +{ +} + +static void +handle_popup_done(void *data, struct wl_shell_surface *shell_surface) +{ +} + +static const struct wl_shell_surface_listener shell_surface_listener = { + handle_ping, + handle_configure, + handle_popup_done +}; + +static struct window * +create_window(struct display *display, int width, int height) +{ + struct window *window; + + window = xzalloc(sizeof *window); + window->display = display; + window->width = width; + window->height = height; + window->surface = wl_compositor_create_surface(display->compositor); + window->shell_surface = wl_shell_get_shell_surface(display->shell, + window->surface); + + if (window->shell_surface) + wl_shell_surface_add_listener(window->shell_surface, + &shell_surface_listener, window); + + wl_shell_surface_set_title(window->shell_surface, "simple-shm"); + + wl_shell_surface_set_toplevel(window->shell_surface); + + wl_surface_damage(window->surface, 0, 0, width, height); + attach_buffer(window, width, height); + wl_surface_commit(window->surface); + + return window; +} + +static void +destroy_window(struct window *window) +{ + wl_shell_surface_destroy(window->shell_surface); + wl_surface_destroy(window->surface); + free(window); +} + +static void +shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct display *d = data; + + d->formats |= (1 << format); +} + +struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct display *d = data; + + if (strcmp(interface, "wl_compositor") == 0) { + d->compositor = + wl_registry_bind(registry, + id, &wl_compositor_interface, 1); + } else if (strcmp(interface, "wl_shell") == 0) { + d->shell = wl_registry_bind(registry, + id, &wl_shell_interface, 1); + } else if (strcmp(interface, "wl_shm") == 0) { + d->shm = wl_registry_bind(registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(d->shm, &shm_listener, d); + } else if (strcmp(interface, "wl_seat") == 0 && + d->seat == NULL) { + d->seat = wl_registry_bind(registry, + id, &wl_seat_interface, 3); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static struct display * +create_display(void) +{ + struct display *display; + + display = xzalloc(sizeof *display); + display->display = wl_display_connect(NULL); + assert(display->display); + + display->formats = 0; + display->registry = wl_display_get_registry(display->display); + wl_registry_add_listener(display->registry, + ®istry_listener, display); + wl_display_roundtrip(display->display); + if (display->shm == NULL) { + fprintf(stderr, "No wl_shm global\n"); + exit(1); + } + + wl_display_roundtrip(display->display); + + if (!(display->formats & (1 << WL_SHM_FORMAT_XRGB8888))) { + fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); + exit(1); + } + + wl_display_get_fd(display->display); + + wl_list_init(&display->devices); + + return display; +} + +static void +pointer_handle_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx_w, wl_fixed_t sy_w) +{ +} + +static void +pointer_handle_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +pointer_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w) +{ +} + +static void +pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state_w) +{ +} + +static void +pointer_handle_axis(void *data, struct wl_pointer *pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ +} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, +}; + +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, + uint32_t format, int fd, uint32_t size) +{ +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state_w) +{ +} + +static void +keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, +}; + +static void +start_device(struct display *display, struct device *device) +{ + if (display->seat == NULL) + return; + + switch (device->type) { + case KEYBOARD: + if (device->p.keyboard == NULL) { + device->p.keyboard = + wl_seat_get_keyboard(display->seat); + wl_keyboard_add_listener(device->p.keyboard, + &keyboard_listener, + NULL); + } + break; + case POINTER: + if (device->p.pointer == NULL) { + device->p.pointer = + wl_seat_get_pointer(display->seat); + wl_pointer_add_listener(device->p.pointer, + &pointer_listener, + NULL); + } + break; + } +} + +static void +destroy_device(struct device *device) +{ + switch (device->type) { + case KEYBOARD: + if (device->p.keyboard) + wl_keyboard_release(device->p.keyboard); + break; + case POINTER: + if (device->p.pointer) + wl_pointer_release(device->p.pointer); + break; + } + + wl_list_remove(&device->link); + free(device); +} + +static void +destroy_devices(struct display *display) +{ + struct device *device, *tmp; + + wl_list_for_each_safe(device, tmp, &display->devices, link) + destroy_device(device); +} + +static void +destroy_display(struct display *display) +{ + destroy_devices(display); + + if (display->shm) + wl_shm_destroy(display->shm); + + if (display->shell) + wl_shell_destroy(display->shell); + + if (display->seat) + wl_seat_destroy(display->seat); + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + wl_registry_destroy(display->registry); + wl_display_flush(display->display); + wl_display_disconnect(display->display); + free(display); +} + +static int running = 1; + +static void +signal_int(int signum) +{ + running = 0; +} + +static int +create_device(struct display *display, const char *time_desc, int type) +{ + int start_time; + int end_time = -1; + char *tail; + struct device *device; + + if (time_desc == NULL) { + fprintf(stderr, "missing time description\n"); + return -1; + } + + errno = 0; + start_time = strtoul(time_desc, &tail, 10); + if (errno) + goto error; + + if (*tail == ':') { + end_time = strtoul(tail + 1, &tail, 10); + if (errno || *tail != '\0') + goto error; + } else if (*tail != '\0') { + goto error; + } + + device = xzalloc(sizeof *device); + device->type = type; + device->start_time = start_time; + device->end_time = end_time; + wl_list_insert(&display->devices, &device->link); + + return 0; + +error: + fprintf(stderr, "invalid time description\n"); + return -1; +} + +static struct timespec begin_time; + +static void +reset_timer(void) +{ + clock_gettime(CLOCK_MONOTONIC, &begin_time); +} + +static double +read_timer(void) +{ + struct timespec t; + + clock_gettime(CLOCK_MONOTONIC, &t); + return (double)(t.tv_sec - begin_time.tv_sec) + + 1e-9 * (t.tv_nsec - begin_time.tv_nsec); +} + +static void +main_loop(struct display *display) +{ + reset_timer(); + + while (running) { + struct device *device, *tmp; + struct pollfd fds[1]; + double sleep_time = DBL_MAX; + double now; + + if (wl_display_dispatch_pending(display->display) == -1) + break; + if (wl_display_flush(display->display) == -1) + break; + + now = read_timer(); + + wl_list_for_each(device, &display->devices, link) { + double next_time = device->start_time - now; + if (next_time < 0.0) { + sleep_time = 0.0; + break; + } else if (next_time < sleep_time) { + sleep_time = next_time; + } + next_time = device->end_time - now; + if (next_time < 0.0) { + sleep_time = 0.0; + break; + } else if (next_time < sleep_time) { + sleep_time = next_time; + } + } + + fds[0].fd = wl_display_get_fd(display->display); + fds[0].events = POLLIN; + fds[0].revents = 0; + + poll(fds, + sizeof fds / sizeof fds[0], + sleep_time == DBL_MAX ? -1 : ceil(sleep_time * 1000.0)); + + if (fds[0].revents && + wl_display_dispatch(display->display) == -1) + break; + + now = read_timer(); + + wl_list_for_each_safe(device, tmp, &display->devices, link) { + if (device->start_time <= now) + start_device(display, device); + if (device->end_time >= 0 && device->end_time <= now) + destroy_device(device); + } + } +} + +int +main(int argc, char **argv) +{ + struct sigaction sigint; + struct display *display; + struct window *window; + int i; + + display = create_display(); + window = create_window(display, 250, 250); + + for (i = 1; i < argc; i++) { + if (!strncmp(argv[i], "-p", 2)) { + char *arg; + if (argv[i][2]) { + arg = argv[i] + 2; + } else { + arg = argv[i + 1]; + i++; + } + if (create_device(display, arg, POINTER) == -1) + return 1; + } else if (!strncmp(argv[i], "-k", 2)) { + char *arg; + if (argv[i][2]) { + arg = argv[i] + 2; + } else { + arg = argv[i + 1]; + i++; + } + if (create_device(display, arg, KEYBOARD) == -1) + return 1; + } else { + fprintf(stderr, "unknown argument %s\n", argv[i]); + return 1; + } + } + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + + main_loop(display); + + fprintf(stderr, "multi-resource exiting\n"); + destroy_window(window); + destroy_display(display); + + return 0; +} diff --git a/clients/nested-client.c b/clients/nested-client.c new file mode 100644 index 00000000..1161a992 --- /dev/null +++ b/clients/nested-client.c @@ -0,0 +1,363 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +#include +#include + +struct window; +struct seat; + +struct nested_client { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + + EGLDisplay egl_display; + EGLContext egl_context; + EGLConfig egl_config; + EGLSurface egl_surface; + struct program *color_program; + + GLuint vert, frag, program; + GLuint rotation; + GLuint pos; + GLuint col; + + struct wl_surface *surface; + struct wl_egl_window *native; + int width, height; +}; + +#define POS 0 +#define COL 1 + +static GLuint +create_shader(const char *source, GLenum shader_type) +{ + GLuint shader; + GLint status; + + shader = glCreateShader(shader_type); + if (shader == 0) + return 0; + + glShaderSource(shader, 1, (const char **) &source, NULL); + glCompileShader(shader); + + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetShaderInfoLog(shader, 1000, &len, log); + fprintf(stderr, "Error: compiling %s: %*s\n", + shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", + len, log); + return 0; + } + + return shader; +} + +static void +create_program(struct nested_client *client, + const char *vert, const char *frag) +{ + GLint status; + + client->vert = create_shader(vert, GL_VERTEX_SHADER); + client->frag = create_shader(frag, GL_FRAGMENT_SHADER); + + client->program = glCreateProgram(); + glAttachShader(client->program, client->frag); + glAttachShader(client->program, client->vert); + glBindAttribLocation(client->program, POS, "pos"); + glBindAttribLocation(client->program, COL, "color"); + glLinkProgram(client->program); + + glGetProgramiv(client->program, GL_LINK_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetProgramInfoLog(client->program, 1000, &len, log); + fprintf(stderr, "Error: linking:\n%*s\n", len, log); + exit(1); + } + + client->rotation = + glGetUniformLocation(client->program, "rotation"); +} + +static const char vertex_shader_text[] = + "uniform mat4 rotation;\n" + "attribute vec4 pos;\n" + "attribute vec4 color;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_Position = rotation * pos;\n" + " v_color = color;\n" + "}\n"; + +static const char color_fragment_shader_text[] = + "precision mediump float;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_FragColor = v_color;\n" + "}\n"; + +static void +render_triangle(struct nested_client *client, uint32_t time) +{ + static const GLfloat verts[3][2] = { + { -0.5, -0.5 }, + { 0.5, -0.5 }, + { 0, 0.5 } + }; + static const GLfloat colors[3][3] = { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; + GLfloat angle; + GLfloat rotation[4][4] = { + { 1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 1 } + }; + static const int32_t speed_div = 5; + static uint32_t start_time = 0; + + if (client->program == 0) + create_program(client, vertex_shader_text, + color_fragment_shader_text); + + if (start_time == 0) + start_time = time; + + angle = ((time - start_time) / speed_div) % 360 * M_PI / 180.0; + rotation[0][0] = cos(angle); + rotation[0][2] = sin(angle); + rotation[2][0] = -sin(angle); + rotation[2][2] = cos(angle); + + glClearColor(0.4, 0.4, 0.4, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(client->program); + + glViewport(0, 0, client->width, client->height); + + glUniformMatrix4fv(client->rotation, 1, GL_FALSE, + (GLfloat *) rotation); + + glVertexAttribPointer(POS, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(COL, 3, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(POS); + glEnableVertexAttribArray(COL); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + glDisableVertexAttribArray(POS); + glDisableVertexAttribArray(COL); + + glFlush(); +} + +static void +frame_callback(void *data, struct wl_callback *callback, uint32_t time); + +static const struct wl_callback_listener frame_listener = { + frame_callback +}; + +static void +frame_callback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct nested_client *client = data; + + if (callback) + wl_callback_destroy(callback); + + callback = wl_surface_frame(client->surface); + wl_callback_add_listener(callback, &frame_listener, client); + + render_triangle(client, time); + + eglSwapBuffers(client->egl_display, client->egl_surface); +} + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + struct nested_client *client = data; + + if (strcmp(interface, "wl_compositor") == 0) { + client->compositor = + wl_registry_bind(registry, name, + &wl_compositor_interface, 1); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static struct nested_client * +nested_client_create(void) +{ + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + static const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLint major, minor, n; + EGLBoolean ret; + + struct nested_client *client; + + client = malloc(sizeof *client); + if (client == NULL) + return NULL; + + client->width = 250; + client->height = 250; + + client->display = wl_display_connect(NULL); + + client->registry = wl_display_get_registry(client->display); + wl_registry_add_listener(client->registry, + ®istry_listener, client); + + /* get globals */ + wl_display_roundtrip(client->display); + + client->egl_display = eglGetDisplay(client->display); + if (client->egl_display == NULL) + return NULL; + + ret = eglInitialize(client->egl_display, &major, &minor); + if (!ret) + return NULL; + ret = eglBindAPI(EGL_OPENGL_ES_API); + if (!ret) + return NULL; + + ret = eglChooseConfig(client->egl_display, config_attribs, + &client->egl_config, 1, &n); + if (!ret || n != 1) + return NULL; + + client->egl_context = eglCreateContext(client->egl_display, + client->egl_config, + EGL_NO_CONTEXT, + context_attribs); + if (!client->egl_context) + return NULL; + + client->surface = wl_compositor_create_surface(client->compositor); + + client->native = wl_egl_window_create(client->surface, + client->width, client->height); + + client->egl_surface = + eglCreateWindowSurface(client->egl_display, + client->egl_config, + client->native, NULL); + + eglMakeCurrent(client->egl_display, client->egl_surface, + client->egl_surface, client->egl_context); + + wl_egl_window_resize(client->native, + client->width, client->height, 0, 0); + + frame_callback(client, NULL, 0); + + return client; +} + +static void +nested_client_destroy(struct nested_client *client) +{ + eglMakeCurrent(client->egl_display, + EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + + wl_egl_window_destroy(client->native); + + wl_surface_destroy(client->surface); + + if (client->compositor) + wl_compositor_destroy(client->compositor); + + wl_registry_destroy(client->registry); + wl_display_flush(client->display); + wl_display_disconnect(client->display); +} + +int +main(int argc, char **argv) +{ + struct nested_client *client; + int ret = 0; + + if (getenv("WAYLAND_SOCKET") == NULL) { + fprintf(stderr, + "must be run by nested, don't run standalone\n"); + return EXIT_FAILURE; + } + + client = nested_client_create(); + if (client == NULL) + return EXIT_FAILURE; + + while (ret != -1) + ret = wl_display_dispatch(client->display); + + nested_client_destroy(client); + + return 0; +} diff --git a/clients/nested.c b/clients/nested.c new file mode 100644 index 00000000..50852340 --- /dev/null +++ b/clients/nested.c @@ -0,0 +1,632 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#define WL_HIDE_DEPRECATED +#include + +#include "window.h" + +#define MIN(x,y) (((x) < (y)) ? (x) : (y)) + +struct nested { + struct display *display; + struct window *window; + struct widget *widget; + struct wl_display *child_display; + struct task child_task; + + EGLDisplay egl_display; + struct program *texture_program; + + struct wl_list surface_list; + struct wl_list frame_callback_list; +}; + +struct nested_region { + struct wl_resource *resource; + pixman_region32_t region; +}; + +struct nested_surface { + struct wl_resource *resource; + struct wl_resource *buffer_resource; + struct nested *nested; + EGLImageKHR *image; + GLuint texture; + struct wl_list link; + cairo_surface_t *cairo_surface; +}; + +struct nested_frame_callback { + struct wl_resource *resource; + struct wl_list link; +}; + +static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; +static PFNEGLCREATEIMAGEKHRPROC create_image; +static PFNEGLDESTROYIMAGEKHRPROC destroy_image; +static PFNEGLBINDWAYLANDDISPLAYWL bind_display; +static PFNEGLUNBINDWAYLANDDISPLAYWL unbind_display; +static PFNEGLQUERYWAYLANDBUFFERWL query_buffer; + +static void +frame_callback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct nested *nested = data; + struct nested_frame_callback *nc, *next; + + if (callback) + wl_callback_destroy(callback); + + wl_list_for_each_safe(nc, next, &nested->frame_callback_list, link) { + wl_callback_send_done(nc->resource, time); + wl_resource_destroy(nc->resource); + } + wl_list_init(&nested->frame_callback_list); + + /* FIXME: toytoolkit need a pre-block handler where we can + * call this. */ + wl_display_flush_clients(nested->child_display); +} + +static const struct wl_callback_listener frame_listener = { + frame_callback +}; + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct nested *nested = data; + cairo_surface_t *surface; + cairo_t *cr; + struct rectangle allocation; + struct wl_callback *callback; + struct nested_surface *s; + + widget_get_allocation(nested->widget, &allocation); + + surface = window_get_surface(nested->window); + + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_rectangle(cr, + allocation.x, + allocation.y, + allocation.width, + allocation.height); + cairo_set_source_rgba(cr, 0, 0, 0, 0.8); + cairo_fill(cr); + + wl_list_for_each(s, &nested->surface_list, link) { + display_acquire_window_surface(nested->display, + nested->window, NULL); + + glBindTexture(GL_TEXTURE_2D, s->texture); + image_target_texture_2d(GL_TEXTURE_2D, s->image); + + display_release_window_surface(nested->display, + nested->window); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cr, s->cairo_surface, + allocation.x + 10, + allocation.y + 10); + cairo_rectangle(cr, allocation.x + 10, + allocation.y + 10, + allocation.width - 10, + allocation.height - 10); + + cairo_fill(cr); + } + + cairo_destroy(cr); + + cairo_surface_destroy(surface); + + callback = wl_surface_frame(window_get_wl_surface(nested->window)); + wl_callback_add_listener(callback, &frame_listener, nested); +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct nested *nested = data; + + window_schedule_redraw(nested->window); +} + +static void +handle_child_data(struct task *task, uint32_t events) +{ + struct nested *nested = container_of(task, struct nested, child_task); + struct wl_event_loop *loop; + + loop = wl_display_get_event_loop(nested->child_display); + + wl_event_loop_dispatch(loop, -1); + wl_display_flush_clients(nested->child_display); +} + +struct nested_client { + struct wl_client *client; + pid_t pid; +}; + +static struct nested_client * +launch_client(struct nested *nested, const char *path) +{ + int sv[2]; + pid_t pid; + struct nested_client *client; + + client = malloc(sizeof *client); + if (client == NULL) + return NULL; + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { + fprintf(stderr, "launch_client: " + "socketpair failed while launching '%s': %m\n", + path); + free(client); + return NULL; + } + + pid = fork(); + if (pid == -1) { + close(sv[0]); + close(sv[1]); + free(client); + fprintf(stderr, "launch_client: " + "fork failed while launching '%s': %m\n", path); + return NULL; + } + + if (pid == 0) { + int clientfd; + char s[32]; + + /* SOCK_CLOEXEC closes both ends, so we dup the fd to + * get a non-CLOEXEC fd to pass through exec. */ + clientfd = dup(sv[1]); + if (clientfd == -1) { + fprintf(stderr, "compositor: dup failed: %m\n"); + exit(-1); + } + + snprintf(s, sizeof s, "%d", clientfd); + setenv("WAYLAND_SOCKET", s, 1); + + execl(path, path, NULL); + + fprintf(stderr, "compositor: executing '%s' failed: %m\n", + path); + exit(-1); + } + + close(sv[1]); + + client->client = wl_client_create(nested->child_display, sv[0]); + if (!client->client) { + close(sv[0]); + free(client); + fprintf(stderr, "launch_client: " + "wl_client_create failed while launching '%s'.\n", + path); + return NULL; + } + + client->pid = pid; + + return client; +} + +static void +destroy_surface(struct wl_resource *resource) +{ + struct nested_surface *surface = wl_resource_get_user_data(resource); + + free(surface); +} + +static void +surface_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +surface_attach(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *buffer_resource, int32_t sx, int32_t sy) +{ + struct nested_surface *surface = wl_resource_get_user_data(resource); + struct nested *nested = surface->nested; + EGLint format, width, height; + cairo_device_t *device; + + if (surface->buffer_resource) + wl_buffer_send_release(surface->buffer_resource); + + surface->buffer_resource = buffer_resource; + if (!query_buffer(nested->egl_display, (void *) buffer_resource, + EGL_TEXTURE_FORMAT, &format)) { + fprintf(stderr, "attaching non-egl wl_buffer\n"); + return; + } + + if (surface->image != EGL_NO_IMAGE_KHR) + destroy_image(nested->egl_display, surface->image); + if (surface->cairo_surface) + cairo_surface_destroy(surface->cairo_surface); + + switch (format) { + case EGL_TEXTURE_RGB: + case EGL_TEXTURE_RGBA: + break; + default: + fprintf(stderr, "unhandled format: %x\n", format); + return; + } + + surface->image = create_image(nested->egl_display, NULL, + EGL_WAYLAND_BUFFER_WL, buffer_resource, + NULL); + if (surface->image == EGL_NO_IMAGE_KHR) { + fprintf(stderr, "failed to create img\n"); + return; + } + + query_buffer(nested->egl_display, + (void *) buffer_resource, EGL_WIDTH, &width); + query_buffer(nested->egl_display, + (void *) buffer_resource, EGL_HEIGHT, &height); + + device = display_get_cairo_device(nested->display); + surface->cairo_surface = + cairo_gl_surface_create_for_texture(device, + CAIRO_CONTENT_COLOR_ALPHA, + surface->texture, + width, height); + + window_schedule_redraw(nested->window); +} + +static void +surface_damage(struct wl_client *client, + struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ +} + +static void +destroy_frame_callback(struct wl_resource *resource) +{ + struct nested_frame_callback *callback = wl_resource_get_user_data(resource); + + wl_list_remove(&callback->link); + free(callback); +} + +static void +surface_frame(struct wl_client *client, + struct wl_resource *resource, uint32_t id) +{ + struct nested_frame_callback *callback; + struct nested_surface *surface = wl_resource_get_user_data(resource); + struct nested *nested = surface->nested; + + callback = malloc(sizeof *callback); + if (callback == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + callback->resource = wl_resource_create(client, + &wl_callback_interface, 1, id); + wl_resource_set_implementation(callback->resource, NULL, callback, + destroy_frame_callback); + + wl_list_insert(nested->frame_callback_list.prev, &callback->link); +} + +static void +surface_set_opaque_region(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region_resource) +{ + fprintf(stderr, "surface_set_opaque_region\n"); +} + +static void +surface_set_input_region(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region_resource) +{ + fprintf(stderr, "surface_set_input_region\n"); +} + +static void +surface_commit(struct wl_client *client, struct wl_resource *resource) +{ +} + +static void +surface_set_buffer_transform(struct wl_client *client, + struct wl_resource *resource, int transform) +{ + fprintf(stderr, "surface_set_buffer_transform\n"); +} + +static const struct wl_surface_interface surface_interface = { + surface_destroy, + surface_attach, + surface_damage, + surface_frame, + surface_set_opaque_region, + surface_set_input_region, + surface_commit, + surface_set_buffer_transform +}; + +static void +compositor_create_surface(struct wl_client *client, + struct wl_resource *resource, uint32_t id) +{ + struct nested *nested = wl_resource_get_user_data(resource); + struct nested_surface *surface; + + surface = zalloc(sizeof *surface); + if (surface == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + surface->nested = nested; + + display_acquire_window_surface(nested->display, + nested->window, NULL); + + glGenTextures(1, &surface->texture); + glBindTexture(GL_TEXTURE_2D, surface->texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + display_release_window_surface(nested->display, nested->window); + + surface->resource = + wl_resource_create(client, &wl_surface_interface, 1, id); + + wl_resource_set_implementation(surface->resource, + &surface_interface, surface, + destroy_surface); + + wl_list_insert(nested->surface_list.prev, &surface->link); +} + +static void +destroy_region(struct wl_resource *resource) +{ + struct nested_region *region = wl_resource_get_user_data(resource); + + pixman_region32_fini(®ion->region); + free(region); +} + +static void +region_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +region_add(struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + struct nested_region *region = wl_resource_get_user_data(resource); + + pixman_region32_union_rect(®ion->region, ®ion->region, + x, y, width, height); +} + +static void +region_subtract(struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + struct nested_region *region = wl_resource_get_user_data(resource); + pixman_region32_t rect; + + pixman_region32_init_rect(&rect, x, y, width, height); + pixman_region32_subtract(®ion->region, ®ion->region, &rect); + pixman_region32_fini(&rect); +} + +static const struct wl_region_interface region_interface = { + region_destroy, + region_add, + region_subtract +}; + +static void +compositor_create_region(struct wl_client *client, + struct wl_resource *resource, uint32_t id) +{ + struct nested_region *region; + + region = malloc(sizeof *region); + if (region == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + pixman_region32_init(®ion->region); + + region->resource = + wl_resource_create(client, &wl_region_interface, 1, id); + wl_resource_set_implementation(region->resource, ®ion_interface, + region, destroy_region); +} + +static const struct wl_compositor_interface compositor_interface = { + compositor_create_surface, + compositor_create_region +}; + +static void +compositor_bind(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct nested *nested = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &wl_compositor_interface, + MIN(version, 3), id); + wl_resource_set_implementation(resource, &compositor_interface, + nested, NULL); +} + +static int +nested_init_compositor(struct nested *nested) +{ + const char *extensions; + struct wl_event_loop *loop; + int fd, ret; + + wl_list_init(&nested->surface_list); + wl_list_init(&nested->frame_callback_list); + nested->child_display = wl_display_create(); + loop = wl_display_get_event_loop(nested->child_display); + fd = wl_event_loop_get_fd(loop); + nested->child_task.run = handle_child_data; + display_watch_fd(nested->display, fd, + EPOLLIN, &nested->child_task); + + if (!wl_global_create(nested->child_display, + &wl_compositor_interface, 1, + nested, compositor_bind)) + return -1; + + wl_display_init_shm(nested->child_display); + + nested->egl_display = display_get_egl_display(nested->display); + extensions = eglQueryString(nested->egl_display, EGL_EXTENSIONS); + if (strstr(extensions, "EGL_WL_bind_wayland_display") == NULL) { + fprintf(stderr, "no EGL_WL_bind_wayland_display extension\n"); + return -1; + } + + bind_display = (void *) eglGetProcAddress("eglBindWaylandDisplayWL"); + unbind_display = (void *) eglGetProcAddress("eglUnbindWaylandDisplayWL"); + create_image = (void *) eglGetProcAddress("eglCreateImageKHR"); + destroy_image = (void *) eglGetProcAddress("eglDestroyImageKHR"); + query_buffer = (void *) eglGetProcAddress("eglQueryWaylandBufferWL"); + image_target_texture_2d = + (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); + + ret = bind_display(nested->egl_display, nested->child_display); + if (!ret) { + fprintf(stderr, "failed to bind wl_display\n"); + return -1; + } + + return 0; +} + +static struct nested * +nested_create(struct display *display) +{ + struct nested *nested; + + nested = zalloc(sizeof *nested); + if (nested == NULL) + return nested; + + nested->window = window_create(display); + nested->widget = frame_create(nested->window, nested); + window_set_title(nested->window, "Wayland Nested"); + nested->display = display; + + window_set_user_data(nested->window, nested); + widget_set_redraw_handler(nested->widget, redraw_handler); + window_set_keyboard_focus_handler(nested->window, + keyboard_focus_handler); + + nested_init_compositor(nested); + + widget_schedule_resize(nested->widget, 400, 400); + + return nested; +} + +static void +nested_destroy(struct nested *nested) +{ + widget_destroy(nested->widget); + window_destroy(nested->window); + free(nested); +} + +int +main(int argc, char *argv[]) +{ + struct display *display; + struct nested *nested; + + display = display_create(&argc, argv); + if (display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + nested = nested_create(display); + + launch_client(nested, "weston-nested-client"); + + display_run(display); + + nested_destroy(nested); + display_destroy(display); + + return 0; +} diff --git a/clients/resizor.c b/clients/resizor.c new file mode 100644 index 00000000..68e4bf9a --- /dev/null +++ b/clients/resizor.c @@ -0,0 +1,294 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" + +struct spring { + double current; + double target; + double previous; +}; + +struct resizor { + struct display *display; + struct window *window; + struct widget *widget; + struct window *menu; + struct spring width; + struct spring height; + struct wl_callback *frame_callback; +}; + +static void +spring_update(struct spring *spring) +{ + double current, force; + + current = spring->current; + force = (spring->target - current) / 20.0 + + (spring->previous - current); + + spring->current = current + (current - spring->previous) + force; + spring->previous = current; +} + +static int +spring_done(struct spring *spring) +{ + return fabs(spring->previous - spring->target) < 0.1; +} + +static const struct wl_callback_listener listener; + +static void +frame_callback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct resizor *resizor = data; + + assert(!callback || callback == resizor->frame_callback); + + spring_update(&resizor->width); + spring_update(&resizor->height); + + widget_schedule_resize(resizor->widget, + resizor->width.current + 0.5, + resizor->height.current + 0.5); + + if (resizor->frame_callback) { + wl_callback_destroy(resizor->frame_callback); + resizor->frame_callback = NULL; + } + + if (!spring_done(&resizor->width) || !spring_done(&resizor->height)) { + resizor->frame_callback = + wl_surface_frame( + window_get_wl_surface(resizor->window)); + wl_callback_add_listener(resizor->frame_callback, &listener, + resizor); + } +} + +static const struct wl_callback_listener listener = { + frame_callback +}; + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct resizor *resizor = data; + cairo_surface_t *surface; + cairo_t *cr; + struct rectangle allocation; + + widget_get_allocation(resizor->widget, &allocation); + + surface = window_get_surface(resizor->window); + + cr = cairo_create(surface); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_rectangle(cr, + allocation.x, + allocation.y, + allocation.width, + allocation.height); + cairo_set_source_rgba(cr, 0, 0, 0, 0.8); + cairo_fill(cr); + cairo_destroy(cr); + + cairo_surface_destroy(surface); +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct resizor *resizor = data; + + window_schedule_redraw(resizor->window); +} + +static void +key_handler(struct window *window, struct input *input, uint32_t time, + uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, + void *data) +{ + struct resizor *resizor = data; + struct rectangle allocation; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + window_get_allocation(resizor->window, &allocation); + resizor->width.current = allocation.width; + if (spring_done(&resizor->width)) + resizor->width.target = allocation.width; + resizor->height.current = allocation.height; + if (spring_done(&resizor->height)) + resizor->height.target = allocation.height; + + switch (sym) { + case XKB_KEY_Up: + if (allocation.height < 400) + break; + + resizor->height.target = allocation.height - 200; + break; + + case XKB_KEY_Down: + if (allocation.height > 1000) + break; + + resizor->height.target = allocation.height + 200; + break; + + case XKB_KEY_Left: + if (allocation.width < 400) + break; + + resizor->width.target = allocation.width - 200; + break; + + case XKB_KEY_Right: + if (allocation.width > 1000) + break; + + resizor->width.target = allocation.width + 200; + break; + + case XKB_KEY_Escape: + display_exit(resizor->display); + break; + } + + if (!resizor->frame_callback) + frame_callback(resizor, NULL, 0); +} + +static void +menu_func(struct window *window, int index, void *user_data) +{ + fprintf(stderr, "picked entry %d\n", index); +} + +static void +show_menu(struct resizor *resizor, struct input *input, uint32_t time) +{ + int32_t x, y; + static const char *entries[] = { + "Roy", "Pris", "Leon", "Zhora" + }; + + input_get_position(input, &x, &y); + window_show_menu(resizor->display, input, time, resizor->window, + x - 10, y - 10, menu_func, entries, 4); +} + +static void +button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, enum wl_pointer_button_state state, void *data) +{ + struct resizor *resizor = data; + + switch (button) { + case BTN_RIGHT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + show_menu(resizor, input, time); + break; + } +} + +static struct resizor * +resizor_create(struct display *display) +{ + struct resizor *resizor; + + resizor = xzalloc(sizeof *resizor); + resizor->window = window_create(display); + resizor->widget = frame_create(resizor->window, resizor); + window_set_title(resizor->window, "Wayland Resizor"); + resizor->display = display; + + window_set_key_handler(resizor->window, key_handler); + window_set_user_data(resizor->window, resizor); + widget_set_redraw_handler(resizor->widget, redraw_handler); + window_set_keyboard_focus_handler(resizor->window, + keyboard_focus_handler); + + widget_set_button_handler(resizor->widget, button_handler); + + resizor->height.previous = 400; + resizor->height.current = 400; + resizor->height.target = 400; + + resizor->width.previous = 400; + resizor->width.current = 400; + resizor->width.target = 400; + + widget_schedule_resize(resizor->widget, 400, 400); + + return resizor; +} + +static void +resizor_destroy(struct resizor *resizor) +{ + if (resizor->frame_callback) + wl_callback_destroy(resizor->frame_callback); + + widget_destroy(resizor->widget); + window_destroy(resizor->window); + free(resizor); +} + +int +main(int argc, char *argv[]) +{ + struct display *display; + struct resizor *resizor; + + display = display_create(&argc, argv); + if (display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + resizor = resizor_create(display); + + display_run(display); + + resizor_destroy(resizor); + display_destroy(display); + + return 0; +} diff --git a/clients/screenshot.c b/clients/screenshot.c new file mode 100644 index 00000000..2a6d9b32 --- /dev/null +++ b/clients/screenshot.c @@ -0,0 +1,309 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "screenshooter-client-protocol.h" +#include "../shared/os-compatibility.h" + +/* The screenshooter is a good example of a custom object exposed by + * the compositor and serves as a test bed for implementing client + * side marshalling outside libwayland.so */ + +static struct wl_shm *shm; +static struct screenshooter *screenshooter; +static struct wl_list output_list; +int min_x, min_y, max_x, max_y; +int buffer_copy_done; + +struct screenshooter_output { + struct wl_output *output; + struct wl_buffer *buffer; + int width, height, offset_x, offset_y; + void *data; + struct wl_list link; +}; + +static void +display_handle_geometry(void *data, + struct wl_output *wl_output, + int x, + int y, + int physical_width, + int physical_height, + int subpixel, + const char *make, + const char *model, + int transform) +{ + struct screenshooter_output *output; + + output = wl_output_get_user_data(wl_output); + + if (wl_output == output->output) { + output->offset_x = x; + output->offset_y = y; + } +} + +static void * +xmalloc(size_t size) +{ + void *p; + + p = malloc(size); + if (p == NULL) { + fprintf(stderr, "%s: out of memory\n", + program_invocation_short_name); + exit(EXIT_FAILURE); + } + + return p; +} + +static void +display_handle_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int width, + int height, + int refresh) +{ + struct screenshooter_output *output; + + output = wl_output_get_user_data(wl_output); + + if (wl_output == output->output && (flags & WL_OUTPUT_MODE_CURRENT)) { + output->width = width; + output->height = height; + } +} + +static const struct wl_output_listener output_listener = { + display_handle_geometry, + display_handle_mode +}; + +static void +screenshot_done(void *data, struct screenshooter *screenshooter) +{ + buffer_copy_done = 1; +} + +static const struct screenshooter_listener screenshooter_listener = { + screenshot_done +}; + +static void +handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + static struct screenshooter_output *output; + + if (strcmp(interface, "wl_output") == 0) { + output = xmalloc(sizeof *output); + output->output = wl_registry_bind(registry, name, + &wl_output_interface, 1); + wl_list_insert(&output_list, &output->link); + wl_output_add_listener(output->output, &output_listener, output); + } else if (strcmp(interface, "wl_shm") == 0) { + shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + } else if (strcmp(interface, "screenshooter") == 0) { + screenshooter = wl_registry_bind(registry, name, + &screenshooter_interface, 1); + } +} + +static void +handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ + /* XXX: unimplemented */ +} + +static const struct wl_registry_listener registry_listener = { + handle_global, + handle_global_remove +}; + +static struct wl_buffer * +create_shm_buffer(int width, int height, void **data_out) +{ + struct wl_shm_pool *pool; + struct wl_buffer *buffer; + int fd, size, stride; + void *data; + + stride = width * 4; + size = stride * height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %m\n", + size); + return NULL; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %m\n"); + close(fd); + return NULL; + } + + pool = wl_shm_create_pool(shm, fd, size); + close(fd); + buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, + WL_SHM_FORMAT_XRGB8888); + wl_shm_pool_destroy(pool); + + *data_out = data; + + return buffer; +} + +static void +write_png(int width, int height) +{ + int output_stride, buffer_stride, i; + cairo_surface_t *surface; + void *data, *d, *s; + struct screenshooter_output *output, *next; + + buffer_stride = width * 4; + + data = xmalloc(buffer_stride * height); + if (!data) + return; + + wl_list_for_each_safe(output, next, &output_list, link) { + output_stride = output->width * 4; + s = output->data; + d = data + (output->offset_y - min_y) * buffer_stride + + (output->offset_x - min_x) * 4; + + for (i = 0; i < output->height; i++) { + memcpy(d, s, output_stride); + d += buffer_stride; + s += output_stride; + } + + free(output); + } + + surface = cairo_image_surface_create_for_data(data, + CAIRO_FORMAT_ARGB32, + width, height, buffer_stride); + cairo_surface_write_to_png(surface, "wayland-screenshot.png"); + cairo_surface_destroy(surface); + free(data); +} + +static int +set_buffer_size(int *width, int *height) +{ + struct screenshooter_output *output; + min_x = min_y = INT_MAX; + max_x = max_y = INT_MIN; + int position = 0; + + wl_list_for_each_reverse(output, &output_list, link) { + output->offset_x = position; + position += output->width; + } + + wl_list_for_each(output, &output_list, link) { + min_x = MIN(min_x, output->offset_x); + min_y = MIN(min_y, output->offset_y); + max_x = MAX(max_x, output->offset_x + output->width); + max_y = MAX(max_y, output->offset_y + output->height); + } + + if (max_x <= min_x || max_y <= min_y) + return -1; + + *width = max_x - min_x; + *height = max_y - min_y; + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct wl_display *display; + struct wl_registry *registry; + struct screenshooter_output *output; + int width, height; + + if (getenv("WAYLAND_SOCKET") == NULL) { + fprintf(stderr, "%s must be launched by weston.\n" + "Use the MOD+S shortcut to take a screenshot.\n", + program_invocation_short_name); + return -1; + } + + display = wl_display_connect(NULL); + if (display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + wl_list_init(&output_list); + registry = wl_display_get_registry(display); + wl_registry_add_listener(registry, ®istry_listener, NULL); + wl_display_dispatch(display); + wl_display_roundtrip(display); + if (screenshooter == NULL) { + fprintf(stderr, "display doesn't support screenshooter\n"); + return -1; + } + + screenshooter_add_listener(screenshooter, &screenshooter_listener, screenshooter); + + if (set_buffer_size(&width, &height)) + return -1; + + + wl_list_for_each(output, &output_list, link) { + output->buffer = create_shm_buffer(output->width, output->height, &output->data); + screenshooter_shoot(screenshooter, output->output, output->buffer); + buffer_copy_done = 0; + while (!buffer_copy_done) + wl_display_roundtrip(display); + } + + write_png(width, height); + + return 0; +} diff --git a/clients/simple-egl.c b/clients/simple-egl.c new file mode 100644 index 00000000..1fd41379 --- /dev/null +++ b/clients/simple-egl.c @@ -0,0 +1,781 @@ +/* + * Copyright © 2011 Benjamin Franzke + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#ifndef EGL_EXT_swap_buffers_with_damage +#define EGL_EXT_swap_buffers_with_damage 1 +typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC)(EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects); +#endif + +#ifndef EGL_EXT_buffer_age +#define EGL_EXT_buffer_age 1 +#define EGL_BUFFER_AGE_EXT 0x313D +#endif + +struct window; +struct seat; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_touch *touch; + struct wl_keyboard *keyboard; + struct wl_shm *shm; + struct wl_cursor_theme *cursor_theme; + struct wl_cursor *default_cursor; + struct wl_surface *cursor_surface; + struct { + EGLDisplay dpy; + EGLContext ctx; + EGLConfig conf; + } egl; + struct window *window; + + PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage; +}; + +struct geometry { + int width, height; +}; + +struct window { + struct display *display; + struct geometry geometry, window_size; + struct { + GLuint rotation_uniform; + GLuint pos; + GLuint col; + } gl; + + struct wl_egl_window *native; + struct wl_surface *surface; + struct wl_shell_surface *shell_surface; + EGLSurface egl_surface; + struct wl_callback *callback; + int fullscreen, configured, opaque; +}; + +static const char *vert_shader_text = + "uniform mat4 rotation;\n" + "attribute vec4 pos;\n" + "attribute vec4 color;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_Position = rotation * pos;\n" + " v_color = color;\n" + "}\n"; + +static const char *frag_shader_text = + "precision mediump float;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_FragColor = v_color;\n" + "}\n"; + +static int running = 1; + +static void +init_egl(struct display *display, int opaque) +{ + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + const char *extensions; + + EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLint major, minor, n; + EGLBoolean ret; + + if (opaque) + config_attribs[9] = 0; + + display->egl.dpy = eglGetDisplay(display->display); + assert(display->egl.dpy); + + ret = eglInitialize(display->egl.dpy, &major, &minor); + assert(ret == EGL_TRUE); + ret = eglBindAPI(EGL_OPENGL_ES_API); + assert(ret == EGL_TRUE); + + ret = eglChooseConfig(display->egl.dpy, config_attribs, + &display->egl.conf, 1, &n); + assert(ret && n == 1); + + display->egl.ctx = eglCreateContext(display->egl.dpy, + display->egl.conf, + EGL_NO_CONTEXT, context_attribs); + assert(display->egl.ctx); + + display->swap_buffers_with_damage = NULL; + extensions = eglQueryString(display->egl.dpy, EGL_EXTENSIONS); + if (extensions && + strstr(extensions, "EGL_EXT_swap_buffers_with_damage") && + strstr(extensions, "EGL_EXT_buffer_age")) + display->swap_buffers_with_damage = + (PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) + eglGetProcAddress("eglSwapBuffersWithDamageEXT"); + + if (display->swap_buffers_with_damage) + printf("has EGL_EXT_buffer_age and EGL_EXT_swap_buffers_with_damage\n"); + +} + +static void +fini_egl(struct display *display) +{ + eglTerminate(display->egl.dpy); + eglReleaseThread(); +} + +static GLuint +create_shader(struct window *window, const char *source, GLenum shader_type) +{ + GLuint shader; + GLint status; + + shader = glCreateShader(shader_type); + assert(shader != 0); + + glShaderSource(shader, 1, (const char **) &source, NULL); + glCompileShader(shader); + + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetShaderInfoLog(shader, 1000, &len, log); + fprintf(stderr, "Error: compiling %s: %*s\n", + shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", + len, log); + exit(1); + } + + return shader; +} + +static void +init_gl(struct window *window) +{ + GLuint frag, vert; + GLuint program; + GLint status; + + frag = create_shader(window, frag_shader_text, GL_FRAGMENT_SHADER); + vert = create_shader(window, vert_shader_text, GL_VERTEX_SHADER); + + program = glCreateProgram(); + glAttachShader(program, frag); + glAttachShader(program, vert); + glLinkProgram(program); + + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetProgramInfoLog(program, 1000, &len, log); + fprintf(stderr, "Error: linking:\n%*s\n", len, log); + exit(1); + } + + glUseProgram(program); + + window->gl.pos = 0; + window->gl.col = 1; + + glBindAttribLocation(program, window->gl.pos, "pos"); + glBindAttribLocation(program, window->gl.col, "color"); + glLinkProgram(program); + + window->gl.rotation_uniform = + glGetUniformLocation(program, "rotation"); +} + +static void +handle_ping(void *data, struct wl_shell_surface *shell_surface, + uint32_t serial) +{ + wl_shell_surface_pong(shell_surface, serial); +} + +static void +handle_configure(void *data, struct wl_shell_surface *shell_surface, + uint32_t edges, int32_t width, int32_t height) +{ + struct window *window = data; + + if (window->native) + wl_egl_window_resize(window->native, width, height, 0, 0); + + window->geometry.width = width; + window->geometry.height = height; + + if (!window->fullscreen) + window->window_size = window->geometry; +} + +static void +handle_popup_done(void *data, struct wl_shell_surface *shell_surface) +{ +} + +static const struct wl_shell_surface_listener shell_surface_listener = { + handle_ping, + handle_configure, + handle_popup_done +}; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time); + +static void +configure_callback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct window *window = data; + + wl_callback_destroy(callback); + + window->configured = 1; + + if (window->callback == NULL) + redraw(data, NULL, time); +} + +static struct wl_callback_listener configure_callback_listener = { + configure_callback, +}; + +static void +toggle_fullscreen(struct window *window, int fullscreen) +{ + struct wl_callback *callback; + + window->fullscreen = fullscreen; + window->configured = 0; + + if (fullscreen) { + wl_shell_surface_set_fullscreen(window->shell_surface, + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, + 0, NULL); + } else { + wl_shell_surface_set_toplevel(window->shell_surface); + handle_configure(window, window->shell_surface, 0, + window->window_size.width, + window->window_size.height); + } + + callback = wl_display_sync(window->display->display); + wl_callback_add_listener(callback, &configure_callback_listener, + window); +} + +static void +create_surface(struct window *window) +{ + struct display *display = window->display; + EGLBoolean ret; + + window->surface = wl_compositor_create_surface(display->compositor); + window->shell_surface = wl_shell_get_shell_surface(display->shell, + window->surface); + + wl_shell_surface_add_listener(window->shell_surface, + &shell_surface_listener, window); + + window->native = + wl_egl_window_create(window->surface, + window->window_size.width, + window->window_size.height); + window->egl_surface = + eglCreateWindowSurface(display->egl.dpy, + display->egl.conf, + window->native, NULL); + + wl_shell_surface_set_title(window->shell_surface, "simple-egl"); + + ret = eglMakeCurrent(window->display->egl.dpy, window->egl_surface, + window->egl_surface, window->display->egl.ctx); + assert(ret == EGL_TRUE); + + toggle_fullscreen(window, window->fullscreen); +} + +static void +destroy_surface(struct window *window) +{ + /* Required, otherwise segfault in egl_dri2.c: dri2_make_current() + * on eglReleaseThread(). */ + eglMakeCurrent(window->display->egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + + eglDestroySurface(window->display->egl.dpy, window->egl_surface); + wl_egl_window_destroy(window->native); + + wl_shell_surface_destroy(window->shell_surface); + wl_surface_destroy(window->surface); + + if (window->callback) + wl_callback_destroy(window->callback); +} + +static const struct wl_callback_listener frame_listener; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time) +{ + struct window *window = data; + struct display *display = window->display; + static const GLfloat verts[3][2] = { + { -0.5, -0.5 }, + { 0.5, -0.5 }, + { 0, 0.5 } + }; + static const GLfloat colors[3][3] = { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; + GLfloat angle; + GLfloat rotation[4][4] = { + { 1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 1 } + }; + static const int32_t speed_div = 5; + static uint32_t start_time = 0; + struct wl_region *region; + EGLint rect[4]; + EGLint buffer_age = 0; + + assert(window->callback == callback); + window->callback = NULL; + + if (callback) + wl_callback_destroy(callback); + + if (!window->configured) + return; + + if (start_time == 0) + start_time = time; + + angle = ((time-start_time) / speed_div) % 360 * M_PI / 180.0; + rotation[0][0] = cos(angle); + rotation[0][2] = sin(angle); + rotation[2][0] = -sin(angle); + rotation[2][2] = cos(angle); + + if (display->swap_buffers_with_damage) + eglQuerySurface(display->egl.dpy, window->egl_surface, + EGL_BUFFER_AGE_EXT, &buffer_age); + + glViewport(0, 0, window->geometry.width, window->geometry.height); + + glUniformMatrix4fv(window->gl.rotation_uniform, 1, GL_FALSE, + (GLfloat *) rotation); + + glClearColor(0.0, 0.0, 0.0, 0.5); + glClear(GL_COLOR_BUFFER_BIT); + + glVertexAttribPointer(window->gl.pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(window->gl.col, 3, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(window->gl.pos); + glEnableVertexAttribArray(window->gl.col); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + glDisableVertexAttribArray(window->gl.pos); + glDisableVertexAttribArray(window->gl.col); + + if (window->opaque || window->fullscreen) { + region = wl_compositor_create_region(window->display->compositor); + wl_region_add(region, 0, 0, + window->geometry.width, + window->geometry.height); + wl_surface_set_opaque_region(window->surface, region); + wl_region_destroy(region); + } else { + wl_surface_set_opaque_region(window->surface, NULL); + } + + window->callback = wl_surface_frame(window->surface); + wl_callback_add_listener(window->callback, &frame_listener, window); + + if (display->swap_buffers_with_damage && buffer_age > 0) { + rect[0] = window->geometry.width / 4 - 1; + rect[1] = window->geometry.height / 4 - 1; + rect[2] = window->geometry.width / 2 + 2; + rect[3] = window->geometry.height / 2 + 2; + display->swap_buffers_with_damage(display->egl.dpy, + window->egl_surface, + rect, 1); + } else { + eglSwapBuffers(display->egl.dpy, window->egl_surface); + } +} + +static const struct wl_callback_listener frame_listener = { + redraw +}; + +static void +pointer_handle_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct display *display = data; + struct wl_buffer *buffer; + struct wl_cursor *cursor = display->default_cursor; + struct wl_cursor_image *image; + + if (display->window->fullscreen) + wl_pointer_set_cursor(pointer, serial, NULL, 0, 0); + else if (cursor) { + image = display->default_cursor->images[0]; + buffer = wl_cursor_image_get_buffer(image); + wl_pointer_set_cursor(pointer, serial, + display->cursor_surface, + image->hotspot_x, + image->hotspot_y); + wl_surface_attach(display->cursor_surface, buffer, 0, 0); + wl_surface_damage(display->cursor_surface, 0, 0, + image->width, image->height); + wl_surface_commit(display->cursor_surface); + } +} + +static void +pointer_handle_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +pointer_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t sx, wl_fixed_t sy) +{ +} + +static void +pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) +{ + struct display *display = data; + + if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) + wl_shell_surface_move(display->window->shell_surface, + display->seat, serial); +} + +static void +pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ +} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, +}; + +static void +touch_handle_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, struct wl_surface *surface, + int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct display *d = (struct display *)data; + + wl_shell_surface_move(d->window->shell_surface, d->seat, serial); +} + +static void +touch_handle_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, int32_t id) +{ +} + +static void +touch_handle_motion(void *data, struct wl_touch *wl_touch, + uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ +} + +static void +touch_handle_frame(void *data, struct wl_touch *wl_touch) +{ +} + +static void +touch_handle_cancel(void *data, struct wl_touch *wl_touch) +{ +} + +static const struct wl_touch_listener touch_listener = { + touch_handle_down, + touch_handle_up, + touch_handle_motion, + touch_handle_frame, + touch_handle_cancel, +}; + +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, + uint32_t format, int fd, uint32_t size) +{ +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ + struct display *d = data; + + if (key == KEY_F11 && state) + toggle_fullscreen(d->window, d->window->fullscreen ^ 1); + else if (key == KEY_ESC && state) + running = 0; +} + +static void +keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + struct display *d = data; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !d->pointer) { + d->pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(d->pointer, &pointer_listener, d); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && d->pointer) { + wl_pointer_destroy(d->pointer); + d->pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !d->keyboard) { + d->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(d->keyboard, &keyboard_listener, d); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && d->keyboard) { + wl_keyboard_destroy(d->keyboard); + d->keyboard = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !d->touch) { + d->touch = wl_seat_get_touch(seat); + wl_touch_set_user_data(d->touch, d); + wl_touch_add_listener(d->touch, &touch_listener, d); + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && d->touch) { + wl_touch_destroy(d->touch); + d->touch = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + struct display *d = data; + + if (strcmp(interface, "wl_compositor") == 0) { + d->compositor = + wl_registry_bind(registry, name, + &wl_compositor_interface, 1); + } else if (strcmp(interface, "wl_shell") == 0) { + d->shell = wl_registry_bind(registry, name, + &wl_shell_interface, 1); + } else if (strcmp(interface, "wl_seat") == 0) { + d->seat = wl_registry_bind(registry, name, + &wl_seat_interface, 1); + wl_seat_add_listener(d->seat, &seat_listener, d); + } else if (strcmp(interface, "wl_shm") == 0) { + d->shm = wl_registry_bind(registry, name, + &wl_shm_interface, 1); + d->cursor_theme = wl_cursor_theme_load(NULL, 32, d->shm); + d->default_cursor = + wl_cursor_theme_get_cursor(d->cursor_theme, "left_ptr"); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static void +signal_int(int signum) +{ + running = 0; +} + +static void +usage(int error_code) +{ + fprintf(stderr, "Usage: simple-egl [OPTIONS]\n\n" + " -f\tRun in fullscreen mode\n" + " -o\tCreate an opaque surface\n" + " -h\tThis help text\n\n"); + + exit(error_code); +} + +int +main(int argc, char **argv) +{ + struct sigaction sigint; + struct display display = { 0 }; + struct window window = { 0 }; + int i, ret = 0; + + window.display = &display; + display.window = &window; + window.window_size.width = 250; + window.window_size.height = 250; + + for (i = 1; i < argc; i++) { + if (strcmp("-f", argv[i]) == 0) + window.fullscreen = 1; + else if (strcmp("-o", argv[i]) == 0) + window.opaque = 1; + else if (strcmp("-h", argv[i]) == 0) + usage(EXIT_SUCCESS); + else + usage(EXIT_FAILURE); + } + + display.display = wl_display_connect(NULL); + assert(display.display); + + display.registry = wl_display_get_registry(display.display); + wl_registry_add_listener(display.registry, + ®istry_listener, &display); + + wl_display_dispatch(display.display); + + init_egl(&display, window.opaque); + create_surface(&window); + init_gl(&window); + + display.cursor_surface = + wl_compositor_create_surface(display.compositor); + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + + while (running && ret != -1) + ret = wl_display_dispatch(display.display); + + fprintf(stderr, "simple-egl exiting\n"); + + destroy_surface(&window); + fini_egl(&display); + + wl_surface_destroy(display.cursor_surface); + if (display.cursor_theme) + wl_cursor_theme_destroy(display.cursor_theme); + + if (display.shell) + wl_shell_destroy(display.shell); + + if (display.compositor) + wl_compositor_destroy(display.compositor); + + wl_registry_destroy(display.registry); + wl_display_flush(display.display); + wl_display_disconnect(display.display); + + return 0; +} diff --git a/clients/simple-shm.c b/clients/simple-shm.c new file mode 100644 index 00000000..81bb54ea --- /dev/null +++ b/clients/simple-shm.c @@ -0,0 +1,427 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "../shared/os-compatibility.h" + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct wl_shm *shm; + uint32_t formats; +}; + +struct buffer { + struct wl_buffer *buffer; + void *shm_data; + int busy; +}; + +struct window { + struct display *display; + int width, height; + struct wl_surface *surface; + struct wl_shell_surface *shell_surface; + struct buffer buffers[2]; + struct buffer *prev_buffer; + struct wl_callback *callback; +}; + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + struct buffer *mybuf = data; + + mybuf->busy = 0; +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static int +create_shm_buffer(struct display *display, struct buffer *buffer, + int width, int height, uint32_t format) +{ + struct wl_shm_pool *pool; + int fd, size, stride; + void *data; + + stride = width * 4; + size = stride * height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %m\n", + size); + return -1; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %m\n"); + close(fd); + return -1; + } + + pool = wl_shm_create_pool(display->shm, fd, size); + buffer->buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, + stride, format); + wl_buffer_add_listener(buffer->buffer, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + buffer->shm_data = data; + + return 0; +} + +static void +handle_ping(void *data, struct wl_shell_surface *shell_surface, + uint32_t serial) +{ + wl_shell_surface_pong(shell_surface, serial); +} + +static void +handle_configure(void *data, struct wl_shell_surface *shell_surface, + uint32_t edges, int32_t width, int32_t height) +{ +} + +static void +handle_popup_done(void *data, struct wl_shell_surface *shell_surface) +{ +} + +static const struct wl_shell_surface_listener shell_surface_listener = { + handle_ping, + handle_configure, + handle_popup_done +}; + +static struct window * +create_window(struct display *display, int width, int height) +{ + struct window *window; + + window = calloc(1, sizeof *window); + if (!window) + return NULL; + + window->callback = NULL; + window->display = display; + window->width = width; + window->height = height; + window->surface = wl_compositor_create_surface(display->compositor); + window->shell_surface = wl_shell_get_shell_surface(display->shell, + window->surface); + + if (window->shell_surface) + wl_shell_surface_add_listener(window->shell_surface, + &shell_surface_listener, window); + + wl_shell_surface_set_title(window->shell_surface, "simple-shm"); + + wl_shell_surface_set_toplevel(window->shell_surface); + + return window; +} + +static void +destroy_window(struct window *window) +{ + if (window->callback) + wl_callback_destroy(window->callback); + + if (window->buffers[0].buffer) + wl_buffer_destroy(window->buffers[0].buffer); + if (window->buffers[1].buffer) + wl_buffer_destroy(window->buffers[1].buffer); + + wl_shell_surface_destroy(window->shell_surface); + wl_surface_destroy(window->surface); + free(window); +} + +static struct buffer * +window_next_buffer(struct window *window) +{ + struct buffer *buffer; + int ret = 0; + + if (!window->buffers[0].busy) + buffer = &window->buffers[0]; + else if (!window->buffers[1].busy) + buffer = &window->buffers[1]; + else + return NULL; + + if (!buffer->buffer) { + ret = create_shm_buffer(window->display, buffer, + window->width, window->height, + WL_SHM_FORMAT_XRGB8888); + + if (ret < 0) + return NULL; + + /* paint the padding */ + memset(buffer->shm_data, 0xff, + window->width * window->height * 4); + } + + return buffer; +} + +static void +paint_pixels(void *image, int padding, int width, int height, uint32_t time) +{ + const int halfh = padding + (height - padding * 2) / 2; + const int halfw = padding + (width - padding * 2) / 2; + int ir, or; + uint32_t *pixel = image; + int y; + + /* squared radii thresholds */ + or = (halfw < halfh ? halfw : halfh) - 8; + ir = or - 32; + or *= or; + ir *= ir; + + pixel += padding * width; + for (y = padding; y < height - padding; y++) { + int x; + int y2 = (y - halfh) * (y - halfh); + + pixel += padding; + for (x = padding; x < width - padding; x++) { + uint32_t v; + + /* squared distance from center */ + int r2 = (x - halfw) * (x - halfw) + y2; + + if (r2 < ir) + v = (r2 / 32 + time / 64) * 0x0080401; + else if (r2 < or) + v = (y + time / 32) * 0x0080401; + else + v = (x + time / 16) * 0x0080401; + v &= 0x00ffffff; + + /* cross if compositor uses X from XRGB as alpha */ + if (abs(x - y) > 6 && abs(x + y - height) > 6) + v |= 0xff000000; + + *pixel++ = v; + } + + pixel += padding; + } +} + +static const struct wl_callback_listener frame_listener; + +static void +redraw(void *data, struct wl_callback *callback, uint32_t time) +{ + struct window *window = data; + struct buffer *buffer; + + buffer = window_next_buffer(window); + if (!buffer) { + fprintf(stderr, + !callback ? "Failed to create the first buffer.\n" : + "Both buffers busy at redraw(). Server bug?\n"); + abort(); + } + + paint_pixels(buffer->shm_data, 20, window->width, window->height, time); + + wl_surface_attach(window->surface, buffer->buffer, 0, 0); + wl_surface_damage(window->surface, + 20, 20, window->width - 40, window->height - 40); + + if (callback) + wl_callback_destroy(callback); + + window->callback = wl_surface_frame(window->surface); + wl_callback_add_listener(window->callback, &frame_listener, window); + wl_surface_commit(window->surface); + buffer->busy = 1; +} + +static const struct wl_callback_listener frame_listener = { + redraw +}; + +static void +shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct display *d = data; + + d->formats |= (1 << format); +} + +struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct display *d = data; + + if (strcmp(interface, "wl_compositor") == 0) { + d->compositor = + wl_registry_bind(registry, + id, &wl_compositor_interface, 1); + } else if (strcmp(interface, "wl_shell") == 0) { + d->shell = wl_registry_bind(registry, + id, &wl_shell_interface, 1); + } else if (strcmp(interface, "wl_shm") == 0) { + d->shm = wl_registry_bind(registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(d->shm, &shm_listener, d); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static struct display * +create_display(void) +{ + struct display *display; + + display = malloc(sizeof *display); + if (display == NULL) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + display->display = wl_display_connect(NULL); + assert(display->display); + + display->formats = 0; + display->registry = wl_display_get_registry(display->display); + wl_registry_add_listener(display->registry, + ®istry_listener, display); + wl_display_roundtrip(display->display); + if (display->shm == NULL) { + fprintf(stderr, "No wl_shm global\n"); + exit(1); + } + + wl_display_roundtrip(display->display); + + if (!(display->formats & (1 << WL_SHM_FORMAT_XRGB8888))) { + fprintf(stderr, "WL_SHM_FORMAT_XRGB32 not available\n"); + exit(1); + } + + wl_display_get_fd(display->display); + + return display; +} + +static void +destroy_display(struct display *display) +{ + if (display->shm) + wl_shm_destroy(display->shm); + + if (display->shell) + wl_shell_destroy(display->shell); + + if (display->compositor) + wl_compositor_destroy(display->compositor); + + wl_registry_destroy(display->registry); + wl_display_flush(display->display); + wl_display_disconnect(display->display); + free(display); +} + +static int running = 1; + +static void +signal_int(int signum) +{ + running = 0; +} + +int +main(int argc, char **argv) +{ + struct sigaction sigint; + struct display *display; + struct window *window; + int ret = 0; + + display = create_display(); + window = create_window(display, 250, 250); + if (!window) + return 1; + + sigint.sa_handler = signal_int; + sigemptyset(&sigint.sa_mask); + sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &sigint, NULL); + + /* Initialise damage to full surface, so the padding gets painted */ + wl_surface_damage(window->surface, 0, 0, + window->width, window->height); + + redraw(window, NULL, 0); + + while (running && ret != -1) + ret = wl_display_dispatch(display->display); + + fprintf(stderr, "simple-shm exiting\n"); + destroy_window(window); + destroy_display(display); + + return 0; +} diff --git a/clients/simple-touch.c b/clients/simple-touch.c new file mode 100644 index 00000000..b5a84d78 --- /dev/null +++ b/clients/simple-touch.c @@ -0,0 +1,340 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2011 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "../shared/os-compatibility.h" + +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) + +struct touch { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct wl_shm *shm; + struct wl_seat *seat; + struct wl_touch *wl_touch; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; + struct wl_surface *surface; + struct wl_shell_surface *shell_surface; + struct wl_buffer *buffer; + int has_argb; + int width, height; + void *data; +}; + +static void +create_shm_buffer(struct touch *touch) +{ + struct wl_shm_pool *pool; + int fd, size, stride; + + stride = touch->width * 4; + size = stride * touch->height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %m\n", + size); + exit(1); + } + + touch->data = + mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (touch->data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %m\n"); + close(fd); + exit(1); + } + + pool = wl_shm_create_pool(touch->shm, fd, size); + touch->buffer = + wl_shm_pool_create_buffer(pool, 0, + touch->width, touch->height, stride, + WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + + close(fd); +} + +static void +shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct touch *touch = data; + + if (format == WL_SHM_FORMAT_ARGB8888) + touch->has_argb = 1; +} + +struct wl_shm_listener shm_listener = { + shm_format +}; + + +static void +touch_paint(struct touch *touch, int32_t x, int32_t y, int32_t id) +{ + uint32_t *p, c; + static const uint32_t colors[] = { + 0xffff0000, + 0xffffff00, + 0xff0000ff, + 0xffff00ff, + 0xff00ff00, + 0xff00ffff, + }; + + if (id < (int32_t) ARRAY_LENGTH(colors)) + c = colors[id]; + else + c = 0xffffffff; + + if (x < 2 || x >= touch->width - 2 || + y < 2 || y >= touch->height - 2) + return; + + p = (uint32_t *) touch->data + (x - 2) + (y - 2) * touch->width; + p[2] = c; + p += touch->width; + p[1] = c; + p[2] = c; + p[3] = c; + p += touch->width; + p[0] = c; + p[1] = c; + p[2] = c; + p[3] = c; + p[4] = c; + p += touch->width; + p[1] = c; + p[2] = c; + p[3] = c; + p += touch->width; + p[2] = c; + + wl_surface_attach(touch->surface, touch->buffer, 0, 0); + wl_surface_damage(touch->surface, x - 2, y - 2, 5, 5); + /* todo: We could queue up more damage before committing, if there + * are more input events to handle. + */ + wl_surface_commit(touch->surface); +} + +static void +touch_handle_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, struct wl_surface *surface, + int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct touch *touch = data; + float x = wl_fixed_to_double(x_w); + float y = wl_fixed_to_double(y_w); + + touch_paint(touch, x, y, id); +} + +static void +touch_handle_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, int32_t id) +{ +} + +static void +touch_handle_motion(void *data, struct wl_touch *wl_touch, + uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct touch *touch = data; + float x = wl_fixed_to_double(x_w); + float y = wl_fixed_to_double(y_w); + + touch_paint(touch, x, y, id); +} + +static void +touch_handle_frame(void *data, struct wl_touch *wl_touch) +{ +} + +static void +touch_handle_cancel(void *data, struct wl_touch *wl_touch) +{ +} + +static const struct wl_touch_listener touch_listener = { + touch_handle_down, + touch_handle_up, + touch_handle_motion, + touch_handle_frame, + touch_handle_cancel, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + struct touch *touch = data; + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !touch->wl_touch) { + touch->wl_touch = wl_seat_get_touch(seat); + wl_touch_set_user_data(touch->wl_touch, touch); + wl_touch_add_listener(touch->wl_touch, &touch_listener, touch); + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && touch->wl_touch) { + wl_touch_destroy(touch->wl_touch); + touch->wl_touch = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + +static void +handle_ping(void *data, struct wl_shell_surface *shell_surface, + uint32_t serial) +{ + wl_shell_surface_pong(shell_surface, serial); +} + +static void +handle_configure(void *data, struct wl_shell_surface *shell_surface, + uint32_t edges, int32_t width, int32_t height) +{ +} + +static void +handle_popup_done(void *data, struct wl_shell_surface *shell_surface) +{ +} + +static const struct wl_shell_surface_listener shell_surface_listener = { + handle_ping, + handle_configure, + handle_popup_done +}; + +static void +handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + struct touch *touch = data; + + if (strcmp(interface, "wl_compositor") == 0) { + touch->compositor = + wl_registry_bind(registry, name, + &wl_compositor_interface, 1); + } else if (strcmp(interface, "wl_shell") == 0) { + touch->shell = + wl_registry_bind(registry, name, + &wl_shell_interface, 1); + } else if (strcmp(interface, "wl_shm") == 0) { + touch->shm = wl_registry_bind(registry, name, + &wl_shm_interface, 1); + wl_shm_add_listener(touch->shm, &shm_listener, touch); + } else if (strcmp(interface, "wl_seat") == 0) { + touch->seat = wl_registry_bind(registry, name, + &wl_seat_interface, 1); + wl_seat_add_listener(touch->seat, &seat_listener, touch); + } +} + +static void +handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + handle_global, + handle_global_remove +}; + +static struct touch * +touch_create(int width, int height) +{ + struct touch *touch; + + touch = malloc(sizeof *touch); + if (touch == NULL) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + touch->display = wl_display_connect(NULL); + assert(touch->display); + + touch->has_argb = 0; + touch->registry = wl_display_get_registry(touch->display); + wl_registry_add_listener(touch->registry, ®istry_listener, touch); + wl_display_dispatch(touch->display); + wl_display_roundtrip(touch->display); + + if (!touch->has_argb) { + fprintf(stderr, "WL_SHM_FORMAT_ARGB32 not available\n"); + exit(1); + } + + touch->width = width; + touch->height = height; + touch->surface = wl_compositor_create_surface(touch->compositor); + touch->shell_surface = wl_shell_get_shell_surface(touch->shell, + touch->surface); + create_shm_buffer(touch); + + if (touch->shell_surface) { + wl_shell_surface_add_listener(touch->shell_surface, + &shell_surface_listener, touch); + wl_shell_surface_set_toplevel(touch->shell_surface); + } + + wl_surface_set_user_data(touch->surface, touch); + wl_shell_surface_set_title(touch->shell_surface, "simple-touch"); + + memset(touch->data, 64, width * height * 4); + wl_surface_attach(touch->surface, touch->buffer, 0, 0); + wl_surface_damage(touch->surface, 0, 0, width, height); + wl_surface_commit(touch->surface); + + return touch; +} + +int +main(int argc, char **argv) +{ + struct touch *touch; + int ret = 0; + + touch = touch_create(600, 500); + + while (ret != -1) + ret = wl_display_dispatch(touch->display); + + return 0; +} diff --git a/clients/smoke.c b/clients/smoke.c new file mode 100644 index 00000000..dd5f4bd4 --- /dev/null +++ b/clients/smoke.c @@ -0,0 +1,331 @@ +/* + * Copyright © 2010 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "window.h" + +struct smoke { + struct display *display; + struct window *window; + struct widget *widget; + int width, height; + int current; + uint32_t time; + struct { float *d, *u, *v; } b[2]; +}; + +static void diffuse(struct smoke *smoke, uint32_t time, + float *source, float *dest) +{ + float *s, *d; + int x, y, k, stride; + float t, a = 0.0002; + + stride = smoke->width; + + for (k = 0; k < 5; k++) { + for (y = 1; y < smoke->height - 1; y++) { + s = source + y * stride; + d = dest + y * stride; + for (x = 1; x < smoke->width - 1; x++) { + t = d[x - 1] + d[x + 1] + + d[x - stride] + d[x + stride]; + d[x] = (s[x] + a * t) / (1 + 4 * a) * 0.995; + } + } + } +} + +static void advect(struct smoke *smoke, uint32_t time, + float *uu, float *vv, float *source, float *dest) +{ + float *s, *d; + float *u, *v; + int x, y, stride; + int i, j; + float px, py, fx, fy; + + stride = smoke->width; + + for (y = 1; y < smoke->height - 1; y++) { + d = dest + y * stride; + u = uu + y * stride; + v = vv + y * stride; + + for (x = 1; x < smoke->width - 1; x++) { + px = x - u[x]; + py = y - v[x]; + if (px < 0.5) + px = 0.5; + if (py < 0.5) + py = 0.5; + if (px > smoke->width - 0.5) + px = smoke->width - 0.5; + if (py > smoke->height - 0.5) + py = smoke->height - 0.5; + i = (int) px; + j = (int) py; + fx = px - i; + fy = py - j; + s = source + j * stride + i; + d[x] = (s[0] * (1 - fx) + s[1] * fx) * (1 - fy) + + (s[stride] * (1 - fx) + s[stride + 1] * fx) * fy; + } + } +} + +static void project(struct smoke *smoke, uint32_t time, + float *u, float *v, float *p, float *div) +{ + int x, y, k, l, s; + float h; + + h = 1.0 / smoke->width; + s = smoke->width; + memset(p, 0, smoke->height * smoke->width); + for (y = 1; y < smoke->height - 1; y++) { + l = y * s; + for (x = 1; x < smoke->width - 1; x++) { + div[l + x] = -0.5 * h * (u[l + x + 1] - u[l + x - 1] + + v[l + x + s] - v[l + x - s]); + p[l + x] = 0; + } + } + + for (k = 0; k < 5; k++) { + for (y = 1; y < smoke->height - 1; y++) { + l = y * s; + for (x = 1; x < smoke->width - 1; x++) { + p[l + x] = (div[l + x] + + p[l + x - 1] + + p[l + x + 1] + + p[l + x - s] + + p[l + x + s]) / 4; + } + } + } + + for (y = 1; y < smoke->height - 1; y++) { + l = y * s; + for (x = 1; x < smoke->width - 1; x++) { + u[l + x] -= 0.5 * (p[l + x + 1] - p[l + x - 1]) / h; + v[l + x] -= 0.5 * (p[l + x + s] - p[l + x - s]) / h; + } + } +} + +static void render(struct smoke *smoke, cairo_surface_t *surface) +{ + unsigned char *dest; + int x, y, width, height, stride; + float *s; + uint32_t *d, c, a; + + dest = cairo_image_surface_get_data(surface); + width = cairo_image_surface_get_width(surface); + height = cairo_image_surface_get_height(surface); + stride = cairo_image_surface_get_stride(surface); + + for (y = 1; y < height - 1; y++) { + s = smoke->b[smoke->current].d + y * smoke->height; + d = (uint32_t *) (dest + y * stride); + for (x = 1; x < width - 1; x++) { + c = (int) (s[x] * 800); + if (c > 255) + c = 255; + a = c; + if (a < 0x33) + a = 0x33; + d[x] = (a << 24) | (c << 16) | (c << 8) | c; + } + } +} + +static void +frame_callback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct smoke *smoke = data; + + window_schedule_redraw(smoke->window); + smoke->time = time; + + if (callback) + wl_callback_destroy(callback); +} + +static const struct wl_callback_listener listener = { + frame_callback, +}; + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct smoke *smoke = data; + uint32_t time = smoke->time; + struct wl_callback *callback; + cairo_surface_t *surface; + + diffuse(smoke, time / 30, smoke->b[0].u, smoke->b[1].u); + diffuse(smoke, time / 30, smoke->b[0].v, smoke->b[1].v); + project(smoke, time / 30, + smoke->b[1].u, smoke->b[1].v, + smoke->b[0].u, smoke->b[0].v); + advect(smoke, time / 30, + smoke->b[1].u, smoke->b[1].v, + smoke->b[1].u, smoke->b[0].u); + advect(smoke, time / 30, + smoke->b[1].u, smoke->b[1].v, + smoke->b[1].v, smoke->b[0].v); + project(smoke, time / 30, + smoke->b[0].u, smoke->b[0].v, + smoke->b[1].u, smoke->b[1].v); + + diffuse(smoke, time / 30, smoke->b[0].d, smoke->b[1].d); + advect(smoke, time / 30, + smoke->b[0].u, smoke->b[0].v, + smoke->b[1].d, smoke->b[0].d); + + surface = window_get_surface(smoke->window); + + render(smoke, surface); + + window_damage(smoke->window, 0, 0, smoke->width, smoke->height); + + cairo_surface_destroy(surface); + + callback = wl_surface_frame(window_get_wl_surface(smoke->window)); + wl_callback_add_listener(callback, &listener, smoke); + wl_surface_commit(window_get_wl_surface(smoke->window)); +} + +static void +smoke_motion_handler(struct smoke *smoke, float x, float y) +{ + int i, i0, i1, j, j0, j1, k, d = 5; + + if (x - d < 1) + i0 = 1; + else + i0 = x - d; + if (i0 + 2 * d > smoke->width - 1) + i1 = smoke->width - 1; + else + i1 = i0 + 2 * d; + + if (y - d < 1) + j0 = 1; + else + j0 = y - d; + if (j0 + 2 * d > smoke->height - 1) + j1 = smoke->height - 1; + else + j1 = j0 + 2 * d; + + for (i = i0; i < i1; i++) + for (j = j0; j < j1; j++) { + k = j * smoke->width + i; + smoke->b[0].u[k] += 256 - (random() & 512); + smoke->b[0].v[k] += 256 - (random() & 512); + smoke->b[0].d[k] += 1; + } +} + +static int +mouse_motion_handler(struct widget *widget, struct input *input, + uint32_t time, float x, float y, void *data) +{ + smoke_motion_handler(data, x, y); + + return CURSOR_HAND1; +} + +static void +touch_motion_handler(struct widget *widget, struct input *input, + uint32_t time, int32_t id, float x, float y, void *data) +{ + smoke_motion_handler(data, x, y); +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct smoke *smoke = data; + + /* Dont resize me */ + widget_set_size(smoke->widget, smoke->width, smoke->height); +} + +int main(int argc, char *argv[]) +{ + struct timespec ts; + struct smoke smoke; + struct display *d; + int size; + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + smoke.width = 200; + smoke.height = 200; + smoke.display = d; + smoke.window = window_create(d); + smoke.widget = window_add_widget(smoke.window, &smoke); + window_set_title(smoke.window, "smoke"); + + window_set_buffer_type(smoke.window, WINDOW_BUFFER_TYPE_SHM); + clock_gettime(CLOCK_MONOTONIC, &ts); + srandom(ts.tv_nsec); + + smoke.current = 0; + size = smoke.height * smoke.width; + smoke.b[0].d = calloc(size, sizeof(float)); + smoke.b[0].u = calloc(size, sizeof(float)); + smoke.b[0].v = calloc(size, sizeof(float)); + smoke.b[1].d = calloc(size, sizeof(float)); + smoke.b[1].u = calloc(size, sizeof(float)); + smoke.b[1].v = calloc(size, sizeof(float)); + + widget_set_motion_handler(smoke.widget, mouse_motion_handler); + widget_set_touch_motion_handler(smoke.widget, touch_motion_handler); + widget_set_resize_handler(smoke.widget, resize_handler); + widget_set_redraw_handler(smoke.widget, redraw_handler); + + window_set_user_data(smoke.window, &smoke); + + widget_schedule_resize(smoke.widget, smoke.width, smoke.height); + + display_run(d); + + return 0; +} diff --git a/clients/subsurfaces.c b/clients/subsurfaces.c new file mode 100644 index 00000000..101ff17e --- /dev/null +++ b/clients/subsurfaces.c @@ -0,0 +1,801 @@ +/* + * Copyright © 2010 Intel Corporation + * Copyright © 2011 Benjamin Franzke + * Copyright © 2012-2013 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "window.h" + +#if 0 +#define DBG(fmt, ...) \ + fprintf(stderr, "%d:%s " fmt, __LINE__, __func__, ##__VA_ARGS__) +#else +#define DBG(...) do {} while (0) +#endif + +static int32_t option_red_mode; +static int32_t option_triangle_mode; +static int32_t option_no_triangle; +static int32_t option_help; + +static const struct weston_option options[] = { + { WESTON_OPTION_INTEGER, "red-mode", 'r', &option_red_mode }, + { WESTON_OPTION_INTEGER, "triangle-mode", 't', &option_triangle_mode }, + { WESTON_OPTION_BOOLEAN, "no-triangle", 'n', &option_no_triangle }, + { WESTON_OPTION_BOOLEAN, "help", 'h', &option_help }, +}; + +static enum subsurface_mode +int_to_mode(int32_t i) +{ + switch (i) { + case 0: + return SUBSURFACE_DESYNCHRONIZED; + case 1: + return SUBSURFACE_SYNCHRONIZED; + default: + fprintf(stderr, "error: %d is not a valid commit mode.\n", i); + exit(1); + } +} + +static const char help_text[] = +"Usage: %s [options]\n" +"\n" +" -r, --red-mode=MODE\t\tthe commit mode for the red sub-surface (0)\n" +" -t, --triangle-mode=MODE\tthe commit mode for the GL sub-surface (0)\n" +" -n, --no-triangle\t\tDo not create the GL sub-surface.\n" +"\n" +"The MODE is the wl_subsurface commit mode used by default for the\n" +"given sub-surface. Valid values are the integers:\n" +" 0\tfor desynchronized, i.e. free-running\n" +" 1\tfor synchronized\n" +"\n" +"This program demonstrates sub-surfaces with the toytoolkit.\n" +"The main surface contains the decorations, a green canvas, and a\n" +"green spinner. One sub-surface is red with a red spinner. These\n" +"are rendered with Cairo. The other sub-surface contains a spinning\n" +"triangle rendered in EGL/GLESv2, without Cairo, i.e. it is a raw GL\n" +"widget.\n" +"\n" +"The GL widget animates on its own. The spinners follow wall clock\n" +"time and update only when their surface is repainted, so you see\n" +"which surfaces get redrawn. The red sub-surface animates on its own,\n" +"but can be toggled with the spacebar.\n" +"\n" +"Even though the sub-surfaces attempt to animate on their own, they\n" +"are subject to the commit mode. If commit mode is synchronized,\n" +"they will need a commit on the main surface to actually display.\n" +"You can trigger a main surface repaint, without a resize, by\n" +"hovering the pointer over the title bar buttons.\n" +"\n" +"Resizing will temporarily toggle the commit mode of all sub-surfaces\n" +"to guarantee synchronized rendering on size changes. It also forces\n" +"a repaint of all surfaces.\n" +"\n" +"Using -t1 -r1 is especially useful for trying to catch inconsistent\n" +"rendering and deadlocks, since free-running sub-surfaces would\n" +"immediately hide the problem.\n" +"\n" +"Key controls:\n" +" space - toggle red sub-surface animation loop\n" +" up - step window size shorter\n" +" down - step window size taller\n" +"\n"; + +struct egl_state { + EGLDisplay dpy; + EGLContext ctx; + EGLConfig conf; +}; + +struct triangle_gl_state { + GLuint rotation_uniform; + GLuint pos; + GLuint col; +}; + +struct triangle { + struct egl_state *egl; + + struct wl_surface *wl_surface; + struct wl_egl_window *egl_window; + EGLSurface egl_surface; + int width; + int height; + + struct triangle_gl_state gl; + + struct widget *widget; + uint32_t time; + struct wl_callback *frame_cb; +}; + +/******** Pure EGL/GLESv2/libwayland-client component: ***************/ + +static const char *vert_shader_text = + "uniform mat4 rotation;\n" + "attribute vec4 pos;\n" + "attribute vec4 color;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_Position = rotation * pos;\n" + " v_color = color;\n" + "}\n"; + +static const char *frag_shader_text = + "precision mediump float;\n" + "varying vec4 v_color;\n" + "void main() {\n" + " gl_FragColor = v_color;\n" + "}\n"; + +static void +egl_print_config_info(struct egl_state *egl) +{ + EGLint r, g, b, a; + + printf("Chosen EGL config details:\n"); + + printf("\tRGBA bits"); + if (eglGetConfigAttrib(egl->dpy, egl->conf, EGL_RED_SIZE, &r) && + eglGetConfigAttrib(egl->dpy, egl->conf, EGL_GREEN_SIZE, &g) && + eglGetConfigAttrib(egl->dpy, egl->conf, EGL_BLUE_SIZE, &b) && + eglGetConfigAttrib(egl->dpy, egl->conf, EGL_ALPHA_SIZE, &a)) + printf(": %d %d %d %d\n", r, g, b, a); + else + printf(" unknown\n"); + + printf("\tswap interval range"); + if (eglGetConfigAttrib(egl->dpy, egl->conf, EGL_MIN_SWAP_INTERVAL, &a) && + eglGetConfigAttrib(egl->dpy, egl->conf, EGL_MAX_SWAP_INTERVAL, &b)) + printf(": %d - %d\n", a, b); + else + printf(" unknown\n"); +} + +static struct egl_state * +egl_state_create(struct wl_display *display) +{ + struct egl_state *egl; + + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLint major, minor, n; + EGLBoolean ret; + + egl = calloc(1, sizeof *egl); + assert(egl); + + egl->dpy = eglGetDisplay(display); + assert(egl->dpy); + + ret = eglInitialize(egl->dpy, &major, &minor); + assert(ret == EGL_TRUE); + ret = eglBindAPI(EGL_OPENGL_ES_API); + assert(ret == EGL_TRUE); + + ret = eglChooseConfig(egl->dpy, config_attribs, &egl->conf, 1, &n); + assert(ret && n == 1); + + egl->ctx = eglCreateContext(egl->dpy, egl->conf, + EGL_NO_CONTEXT, context_attribs); + assert(egl->ctx); + egl_print_config_info(egl); + + return egl; +} + +static void +egl_state_destroy(struct egl_state *egl) +{ + /* Required, otherwise segfault in egl_dri2.c: dri2_make_current() + * on eglReleaseThread(). */ + eglMakeCurrent(egl->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + + eglTerminate(egl->dpy); + eglReleaseThread(); + free(egl); +} + +static void +egl_make_swapbuffers_nonblock(struct egl_state *egl) +{ + EGLint a = EGL_MIN_SWAP_INTERVAL; + EGLint b = EGL_MAX_SWAP_INTERVAL; + + if (!eglGetConfigAttrib(egl->dpy, egl->conf, a, &a) || + !eglGetConfigAttrib(egl->dpy, egl->conf, b, &b)) { + fprintf(stderr, "warning: swap interval range unknown\n"); + } else + if (a > 0) { + fprintf(stderr, "warning: minimum swap interval is %d, " + "while 0 is required to not deadlock on resize.\n", a); + } + + /* + * We rely on the Wayland compositor to sync to vblank anyway. + * We just need to be able to call eglSwapBuffers() without the + * risk of waiting for a frame callback in it. + */ + if (!eglSwapInterval(egl->dpy, 0)) { + fprintf(stderr, "error: eglSwapInterval() failed.\n"); + } +} + +static GLuint +create_shader(const char *source, GLenum shader_type) +{ + GLuint shader; + GLint status; + + shader = glCreateShader(shader_type); + assert(shader != 0); + + glShaderSource(shader, 1, (const char **) &source, NULL); + glCompileShader(shader); + + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetShaderInfoLog(shader, 1000, &len, log); + fprintf(stderr, "Error: compiling %s: %*s\n", + shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment", + len, log); + exit(1); + } + + return shader; +} + +static void +triangle_init_gl(struct triangle_gl_state *trigl) +{ + GLuint frag, vert; + GLuint program; + GLint status; + + frag = create_shader(frag_shader_text, GL_FRAGMENT_SHADER); + vert = create_shader(vert_shader_text, GL_VERTEX_SHADER); + + program = glCreateProgram(); + glAttachShader(program, frag); + glAttachShader(program, vert); + glLinkProgram(program); + + glGetProgramiv(program, GL_LINK_STATUS, &status); + if (!status) { + char log[1000]; + GLsizei len; + glGetProgramInfoLog(program, 1000, &len, log); + fprintf(stderr, "Error: linking:\n%*s\n", len, log); + exit(1); + } + + glUseProgram(program); + + trigl->pos = 0; + trigl->col = 1; + + glBindAttribLocation(program, trigl->pos, "pos"); + glBindAttribLocation(program, trigl->col, "color"); + glLinkProgram(program); + + trigl->rotation_uniform = glGetUniformLocation(program, "rotation"); +} + +static void +triangle_draw(const struct triangle_gl_state *trigl, uint32_t time) +{ + static const GLfloat verts[3][2] = { + { -0.5, -0.5 }, + { 0.5, -0.5 }, + { 0, 0.5 } + }; + static const GLfloat colors[3][3] = { + { 1, 0, 0 }, + { 0, 1, 0 }, + { 0, 0, 1 } + }; + GLfloat angle; + GLfloat rotation[4][4] = { + { 1, 0, 0, 0 }, + { 0, 1, 0, 0 }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 1 } + }; + static const int32_t speed_div = 5; + + angle = (time / speed_div) % 360 * M_PI / 180.0; + rotation[0][0] = cos(angle); + rotation[0][2] = sin(angle); + rotation[2][0] = -sin(angle); + rotation[2][2] = cos(angle); + + glUniformMatrix4fv(trigl->rotation_uniform, 1, GL_FALSE, + (GLfloat *) rotation); + + glClearColor(0.0, 0.0, 0.0, 0.5); + glClear(GL_COLOR_BUFFER_BIT); + + glVertexAttribPointer(trigl->pos, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(trigl->col, 3, GL_FLOAT, GL_FALSE, 0, colors); + glEnableVertexAttribArray(trigl->pos); + glEnableVertexAttribArray(trigl->col); + + glDrawArrays(GL_TRIANGLES, 0, 3); + + glDisableVertexAttribArray(trigl->pos); + glDisableVertexAttribArray(trigl->col); +} + +static void +triangle_frame_callback(void *data, struct wl_callback *callback, + uint32_t time); + +static const struct wl_callback_listener triangle_frame_listener = { + triangle_frame_callback +}; + +static void +triangle_frame_callback(void *data, struct wl_callback *callback, + uint32_t time) +{ + struct triangle *tri = data; + + DBG("%stime %u\n", callback ? "" : "artificial ", time); + assert(callback == tri->frame_cb); + tri->time = time; + + if (callback) + wl_callback_destroy(callback); + + eglMakeCurrent(tri->egl->dpy, tri->egl_surface, + tri->egl_surface, tri->egl->ctx); + + glViewport(0, 0, tri->width, tri->height); + + triangle_draw(&tri->gl, tri->time); + + tri->frame_cb = wl_surface_frame(tri->wl_surface); + wl_callback_add_listener(tri->frame_cb, &triangle_frame_listener, tri); + + eglSwapBuffers(tri->egl->dpy, tri->egl_surface); +} + +static void +triangle_create_egl_surface(struct triangle *tri, int width, int height) +{ + EGLBoolean ret; + + tri->wl_surface = widget_get_wl_surface(tri->widget); + tri->egl_window = wl_egl_window_create(tri->wl_surface, width, height); + tri->egl_surface = eglCreateWindowSurface(tri->egl->dpy, + tri->egl->conf, + tri->egl_window, NULL); + + ret = eglMakeCurrent(tri->egl->dpy, tri->egl_surface, + tri->egl_surface, tri->egl->ctx); + assert(ret == EGL_TRUE); + + egl_make_swapbuffers_nonblock(tri->egl); + triangle_init_gl(&tri->gl); +} + +/********* The widget code interfacing the toolkit agnostic code: **********/ + +static void +triangle_resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct triangle *tri = data; + + DBG("to %dx%d\n", width, height); + tri->width = width; + tri->height = height; + + if (tri->egl_surface) { + wl_egl_window_resize(tri->egl_window, width, height, 0, 0); + } else { + triangle_create_egl_surface(tri, width, height); + triangle_frame_callback(tri, NULL, 0); + } +} + +static void +triangle_redraw_handler(struct widget *widget, void *data) +{ + struct triangle *tri = data; + int w, h; + + wl_egl_window_get_attached_size(tri->egl_window, &w, &h); + + DBG("previous %dx%d, new %dx%d\n", w, h, tri->width, tri->height); + + /* If size is not changing, do not redraw ahead of time. + * That would risk blocking in eglSwapbuffers(). + */ + if (w == tri->width && h == tri->height) + return; + + if (tri->frame_cb) { + wl_callback_destroy(tri->frame_cb); + tri->frame_cb = NULL; + } + triangle_frame_callback(tri, NULL, tri->time); +} + +static void +set_empty_input_region(struct widget *widget, struct display *display) +{ + struct wl_compositor *compositor; + struct wl_surface *surface; + struct wl_region *region; + + compositor = display_get_compositor(display); + surface = widget_get_wl_surface(widget); + region = wl_compositor_create_region(compositor); + wl_surface_set_input_region(surface, region); + wl_region_destroy(region); +} + +static struct triangle * +triangle_create(struct window *window, struct egl_state *egl) +{ + struct triangle *tri; + + tri = xmalloc(sizeof *tri); + memset(tri, 0, sizeof *tri); + + tri->egl = egl; + tri->widget = window_add_subsurface(window, tri, + int_to_mode(option_triangle_mode)); + widget_set_resize_handler(tri->widget, triangle_resize_handler); + widget_set_redraw_handler(tri->widget, triangle_redraw_handler); + + set_empty_input_region(tri->widget, window_get_display(window)); + + return tri; +} + +static void +triangle_destroy(struct triangle *tri) +{ + if (tri->egl_surface) + eglDestroySurface(tri->egl->dpy, tri->egl_surface); + + if (tri->egl_window) + wl_egl_window_destroy(tri->egl_window); + + widget_destroy(tri->widget); + free(tri); +} + +/************** The toytoolkit application code: *********************/ + +struct demoapp { + struct display *display; + struct window *window; + struct widget *widget; + struct widget *subsurface; + + struct egl_state *egl; + struct triangle *triangle; + + int animate; +}; + +static void +draw_spinner(cairo_t *cr, const struct rectangle *rect, uint32_t time) +{ + double cx, cy, r, angle; + unsigned t; + + cx = rect->x + rect->width / 2; + cy = rect->y + rect->height / 2; + r = (rect->width < rect->height ? rect->width : rect->height) * 0.3; + t = time % 2000; + angle = t * (M_PI / 500.0); + + cairo_set_line_width(cr, 4.0); + + if (t < 1000) + cairo_arc(cr, cx, cy, r, 0.0, angle); + else + cairo_arc(cr, cx, cy, r, angle, 0.0); + + cairo_stroke(cr); +} + +static void +sub_redraw_handler(struct widget *widget, void *data) +{ + struct demoapp *app = data; + cairo_t *cr; + struct rectangle allocation; + uint32_t time; + + widget_get_allocation(app->subsurface, &allocation); + + cr = widget_cairo_create(widget); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + + /* debug: paint whole surface magenta; no magenta should show */ + cairo_set_source_rgba(cr, 0.9, 0.0, 0.9, 1.0); + cairo_paint(cr); + + cairo_rectangle(cr, + allocation.x, + allocation.y, + allocation.width, + allocation.height); + cairo_clip(cr); + + cairo_set_source_rgba(cr, 0.8, 0, 0, 0.8); + cairo_paint(cr); + + time = widget_get_last_time(widget); + cairo_set_source_rgba(cr, 1.0, 0.5, 0.5, 1.0); + draw_spinner(cr, &allocation, time); + + cairo_destroy(cr); + + if (app->animate) + widget_schedule_redraw(app->subsurface); + DBG("%dx%d @ %d,%d, last time %u\n", + allocation.width, allocation.height, + allocation.x, allocation.y, time); +} + +static void +sub_resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + DBG("%dx%d\n", width, height); + widget_input_region_add(widget, NULL); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct demoapp *app = data; + cairo_t *cr; + struct rectangle allocation; + uint32_t time; + + widget_get_allocation(app->widget, &allocation); + + cr = widget_cairo_create(widget); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_rectangle(cr, + allocation.x, + allocation.y, + allocation.width, + allocation.height); + cairo_set_source_rgba(cr, 0, 0.8, 0, 0.8); + cairo_fill(cr); + + time = widget_get_last_time(widget); + cairo_set_source_rgba(cr, 0.5, 1.0, 0.5, 1.0); + draw_spinner(cr, &allocation, time); + + cairo_destroy(cr); + + DBG("%dx%d @ %d,%d, last time %u\n", + allocation.width, allocation.height, + allocation.x, allocation.y, time); +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct demoapp *app = data; + struct rectangle area; + int side, h; + + widget_get_allocation(widget, &area); + + side = area.width < area.height ? area.width / 2 : area.height / 2; + h = area.height - side; + + widget_set_allocation(app->subsurface, + area.x + area.width - side, + area.y, + side, h); + + if (app->triangle) { + widget_set_allocation(app->triangle->widget, + area.x + area.width - side, + area.y + h, + side, side); + } + + DBG("green %dx%d, red %dx%d, GL %dx%d\n", + area.width, area.height, side, h, side, side); +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct demoapp *app = data; + + window_schedule_redraw(app->window); +} + +static void +key_handler(struct window *window, struct input *input, uint32_t time, + uint32_t key, uint32_t sym, + enum wl_keyboard_key_state state, void *data) +{ + struct demoapp *app = data; + struct rectangle winrect; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + switch (sym) { + case XKB_KEY_space: + app->animate = !app->animate; + window_schedule_redraw(window); + break; + case XKB_KEY_Up: + window_get_allocation(window, &winrect); + winrect.height -= 100; + if (winrect.height < 150) + winrect.height = 150; + window_schedule_resize(window, winrect.width, winrect.height); + break; + case XKB_KEY_Down: + window_get_allocation(window, &winrect); + winrect.height += 100; + if (winrect.height > 600) + winrect.height = 600; + window_schedule_resize(window, winrect.width, winrect.height); + break; + case XKB_KEY_Escape: + display_exit(app->display); + break; + } +} + +static struct demoapp * +demoapp_create(struct display *display) +{ + struct demoapp *app; + + app = xmalloc(sizeof *app); + memset(app, 0, sizeof *app); + + app->egl = egl_state_create(display_get_display(display)); + + app->display = display; + display_set_user_data(app->display, app); + + app->window = window_create(app->display); + app->widget = frame_create(app->window, app); + window_set_title(app->window, "Wayland Sub-surface Demo"); + + window_set_key_handler(app->window, key_handler); + window_set_user_data(app->window, app); + window_set_keyboard_focus_handler(app->window, keyboard_focus_handler); + + widget_set_redraw_handler(app->widget, redraw_handler); + widget_set_resize_handler(app->widget, resize_handler); + + app->subsurface = window_add_subsurface(app->window, app, + int_to_mode(option_red_mode)); + widget_set_redraw_handler(app->subsurface, sub_redraw_handler); + widget_set_resize_handler(app->subsurface, sub_resize_handler); + + if (app->egl && !option_no_triangle) + app->triangle = triangle_create(app->window, app->egl); + + /* minimum size */ + widget_schedule_resize(app->widget, 100, 100); + + /* initial size */ + widget_schedule_resize(app->widget, 400, 300); + + app->animate = 1; + + return app; +} + +static void +demoapp_destroy(struct demoapp *app) +{ + if (app->triangle) + triangle_destroy(app->triangle); + + if (app->egl) + egl_state_destroy(app->egl); + + widget_destroy(app->subsurface); + widget_destroy(app->widget); + window_destroy(app->window); + free(app); +} + +int +main(int argc, char *argv[]) +{ + struct display *display; + struct demoapp *app; + + parse_options(options, ARRAY_LENGTH(options), &argc, argv); + if (option_help) { + printf(help_text, argv[0]); + return 0; + } + + display = display_create(&argc, argv); + if (display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + if (!display_has_subcompositor(display)) { + fprintf(stderr, "compositor does not support " + "the subcompositor extension\n"); + return -1; + } + + app = demoapp_create(display); + + display_run(display); + + demoapp_destroy(app); + display_destroy(display); + + return 0; +} diff --git a/clients/tablet-shell.c b/clients/tablet-shell.c new file mode 100644 index 00000000..45733b11 --- /dev/null +++ b/clients/tablet-shell.c @@ -0,0 +1,478 @@ +/* + * Copyright © 2011, 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "window.h" +#include "../shared/cairo-util.h" +#include "../shared/config-parser.h" + +#include "tablet-shell-client-protocol.h" + +struct tablet { + struct display *display; + struct tablet_shell *tablet_shell; + struct rectangle allocation; + struct window *switcher; + + struct homescreen *homescreen; + struct lockscreen *lockscreen; +}; + +struct homescreen { + struct window *window; + struct widget *widget; + struct wl_list launcher_list; +}; + +struct lockscreen { + struct window *window; + struct widget *widget; +}; + +struct launcher { + struct widget *widget; + struct homescreen *homescreen; + cairo_surface_t *icon; + int focused, pressed; + char *path; + struct wl_list link; +}; + +static char *key_lockscreen_icon; +static char *key_lockscreen_background; +static char *key_homescreen_background; + +static void +sigchild_handler(int s) +{ + int status; + pid_t pid; + + while (pid = waitpid(-1, &status, WNOHANG), pid > 0) + fprintf(stderr, "child %d exited\n", pid); +} + +static void +paint_background(cairo_t *cr, const char *path, struct rectangle *allocation) +{ + cairo_surface_t *image = NULL; + cairo_pattern_t *pattern; + cairo_matrix_t matrix; + double sx, sy; + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + if (path) + image = load_cairo_surface(path); + if (image) { + pattern = cairo_pattern_create_for_surface(image); + sx = (double) cairo_image_surface_get_width(image) / + allocation->width; + sy = (double) cairo_image_surface_get_height(image) / + allocation->height; + cairo_matrix_init_scale(&matrix, sx, sy); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_set_source(cr, pattern); + cairo_pattern_destroy (pattern); + cairo_surface_destroy(image); + cairo_paint(cr); + } else { + fprintf(stderr, "couldn't load background image: %s\n", path); + cairo_set_source_rgb(cr, 0.2, 0, 0); + cairo_paint(cr); + } +} + +static void +homescreen_draw(struct widget *widget, void *data) +{ + struct homescreen *homescreen = data; + cairo_surface_t *surface; + struct rectangle allocation; + cairo_t *cr; + struct launcher *launcher; + const int rows = 4, columns = 5, icon_width = 128, icon_height = 128; + int x, y, i, width, height, vmargin, hmargin, vpadding, hpadding; + + surface = window_get_surface(homescreen->window); + cr = cairo_create(surface); + + widget_get_allocation(widget, &allocation); + paint_background(cr, key_homescreen_background, &allocation); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + width = allocation.width - columns * icon_width; + hpadding = width / (columns + 1); + hmargin = (width - hpadding * (columns - 1)) / 2; + + height = allocation.height - rows * icon_height; + vpadding = height / (rows + 1); + vmargin = (height - vpadding * (rows - 1)) / 2; + + x = hmargin; + y = vmargin; + i = 0; + + wl_list_for_each(launcher, &homescreen->launcher_list, link) { + widget_set_allocation(launcher->widget, + x, y, icon_width, icon_height); + x += icon_width + hpadding; + i++; + if (i == columns) { + x = hmargin; + y += icon_height + vpadding; + i = 0; + } + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static void +lockscreen_draw(struct widget *widget, void *data) +{ + struct lockscreen *lockscreen = data; + cairo_surface_t *surface; + cairo_surface_t *icon; + struct rectangle allocation; + cairo_t *cr; + int width, height; + + surface = window_get_surface(lockscreen->window); + cr = cairo_create(surface); + + widget_get_allocation(widget, &allocation); + paint_background(cr, key_lockscreen_background, &allocation); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + icon = load_cairo_surface(key_lockscreen_icon); + if (icon) { + width = cairo_image_surface_get_width(icon); + height = cairo_image_surface_get_height(icon); + cairo_set_source_surface(cr, icon, + allocation.x + (allocation.width - width) / 2, + allocation.y + (allocation.height - height) / 2); + } else { + fprintf(stderr, "couldn't load lockscreen icon: %s\n", + key_lockscreen_icon); + cairo_set_source_rgb(cr, 0.2, 0, 0); + } + cairo_paint(cr); + cairo_destroy(cr); + cairo_surface_destroy(icon); + cairo_surface_destroy(surface); +} + +static void +lockscreen_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct lockscreen *lockscreen = data; + + if (state == WL_POINTER_BUTTON_STATE_PRESSED && lockscreen->window) { + window_destroy(lockscreen->window); + lockscreen->window = NULL; + } +} + +static struct homescreen * +homescreen_create(struct tablet *tablet) +{ + struct homescreen *homescreen; + + homescreen = zalloc (sizeof *homescreen); + homescreen->window = window_create_custom(tablet->display); + homescreen->widget = + window_add_widget(homescreen->window, homescreen); + window_set_user_data(homescreen->window, homescreen); + window_set_title(homescreen->window, "homescreen"); + widget_set_redraw_handler(homescreen->widget, homescreen_draw); + + return homescreen; +} + +static struct lockscreen * +lockscreen_create(struct tablet *tablet) +{ + struct lockscreen *lockscreen; + + lockscreen = zalloc (sizeof *lockscreen); + lockscreen->window = window_create_custom(tablet->display); + lockscreen->widget = + window_add_widget(lockscreen->window, lockscreen); + window_set_user_data(lockscreen->window, lockscreen); + window_set_title(lockscreen->window, "lockscreen"); + widget_set_redraw_handler(lockscreen->widget, lockscreen_draw); + widget_set_button_handler(lockscreen->widget, + lockscreen_button_handler); + + return lockscreen; +} + +static void +show_lockscreen(void *data, struct tablet_shell *tablet_shell) +{ + struct tablet *tablet = data; + + tablet->lockscreen = lockscreen_create(tablet); + tablet_shell_set_lockscreen(tablet->tablet_shell, + window_get_wl_surface(tablet->lockscreen->window)); + + widget_schedule_resize(tablet->lockscreen->widget, + tablet->allocation.width, + tablet->allocation.height); +} + +static void +show_switcher(void *data, struct tablet_shell *tablet_shell) +{ + struct tablet *tablet = data; + + tablet->switcher = window_create_custom(tablet->display); + window_set_user_data(tablet->switcher, tablet); + tablet_shell_set_switcher(tablet->tablet_shell, + window_get_wl_surface(tablet->switcher)); +} + +static void +hide_switcher(void *data, struct tablet_shell *tablet_shell) +{ +} + +static const struct tablet_shell_listener tablet_shell_listener = { + show_lockscreen, + show_switcher, + hide_switcher +}; + +static int +launcher_enter_handler(struct widget *widget, struct input *input, + float x, float y, void *data) +{ + struct launcher *launcher = data; + + launcher->focused = 1; + widget_schedule_redraw(widget); + + return CURSOR_LEFT_PTR; +} + +static void +launcher_leave_handler(struct widget *widget, + struct input *input, void *data) +{ + struct launcher *launcher = data; + + launcher->focused = 0; + widget_schedule_redraw(widget); +} + +static void +launcher_activate(struct launcher *widget) +{ + pid_t pid; + + pid = fork(); + if (pid < 0) { + fprintf(stderr, "fork failed: %m\n"); + return; + } + + if (pid) + return; + + if (execl(widget->path, widget->path, NULL) < 0) { + fprintf(stderr, "execl '%s' failed: %m\n", widget->path); + exit(1); + } +} + +static void +launcher_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct launcher *launcher; + + launcher = widget_get_user_data(widget); + widget_schedule_redraw(widget); + if (state == WL_POINTER_BUTTON_STATE_RELEASED) { + launcher_activate(launcher); + launcher->pressed = 0; + } else if (state == WL_POINTER_BUTTON_STATE_PRESSED) + launcher->pressed = 1; +} + +static void +launcher_redraw_handler(struct widget *widget, void *data) +{ + struct launcher *launcher = data; + cairo_surface_t *surface; + struct rectangle allocation; + cairo_t *cr; + + surface = window_get_surface(launcher->homescreen->window); + cr = cairo_create(surface); + + widget_get_allocation(widget, &allocation); + if (launcher->pressed) { + allocation.x++; + allocation.y++; + } + + cairo_set_source_surface(cr, launcher->icon, + allocation.x, allocation.y); + cairo_paint(cr); + + if (launcher->focused) { + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.4); + cairo_mask_surface(cr, launcher->icon, + allocation.x, allocation.y); + } + + cairo_destroy(cr); +} + +static void +tablet_shell_add_launcher(struct tablet *tablet, + const char *icon, const char *path) +{ + struct launcher *launcher; + struct homescreen *homescreen = tablet->homescreen; + + launcher = xmalloc(sizeof *launcher); + launcher->icon = load_cairo_surface(icon); + if ( !launcher->icon || + cairo_surface_status (launcher->icon) != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "couldn't load %s\n", icon); + free(launcher); + return; + } + launcher->path = strdup(path); + + launcher->homescreen = homescreen; + launcher->widget = widget_add_widget(homescreen->widget, launcher); + widget_set_enter_handler(launcher->widget, + launcher_enter_handler); + widget_set_leave_handler(launcher->widget, + launcher_leave_handler); + widget_set_button_handler(launcher->widget, + launcher_button_handler); + widget_set_redraw_handler(launcher->widget, + launcher_redraw_handler); + + wl_list_insert(&homescreen->launcher_list, &launcher->link); +} + +static void +global_handler(struct display *display, uint32_t name, + const char *interface, uint32_t version, void *data) +{ + struct tablet *tablet = data; + + if (!strcmp(interface, "tablet_shell")) { + tablet->tablet_shell = + display_bind(display, name, + &tablet_shell_interface, 1); + tablet_shell_add_listener(tablet->tablet_shell, + &tablet_shell_listener, tablet); + } +} + +int main(int argc, char *argv[]) +{ + struct tablet tablet = { 0 }; + struct display *display; + struct output *output; + struct weston_config *config; + struct weston_config_section *s; + char *icon, *path; + const char *name; + + display = display_create(&argc, argv); + if (display == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + tablet.display = display; + + display_set_user_data(tablet.display, &tablet); + display_set_global_handler(tablet.display, global_handler); + + tablet.homescreen = homescreen_create(&tablet); + tablet_shell_set_homescreen(tablet.tablet_shell, + window_get_wl_surface(tablet.homescreen->window)); + + wl_display_roundtrip (display_get_display(tablet.display)); + + wl_list_init(&tablet.homescreen->launcher_list); + + config = weston_config_parse("weston.ini"); + s = weston_config_get_section(config, "shell", NULL, NULL); + weston_config_section_get_string(s, "lockscreen-icon", + &key_lockscreen_icon, NULL); + weston_config_section_get_string(s, "lockscreen", + &key_lockscreen_background, NULL); + weston_config_section_get_string(s, "homescreen", + &key_homescreen_background, NULL); + + s = NULL; + while (weston_config_next_section(config, &s, &name)) { + if (strcmp(name, "launcher") != 0) + continue; + + weston_config_section_get_string(s, "icon", &icon, NULL); + weston_config_section_get_string(s, "path", &path, NULL); + + if (icon != NULL && path != NULL) + tablet_shell_add_launcher(&tablet, icon, path); + else + fprintf(stderr, "invalid launcher section\n"); + + free(icon); + free(path); + } + + weston_config_destroy(config); + + signal(SIGCHLD, sigchild_handler); + + output = display_get_output(tablet.display); + output_get_allocation(output, &tablet.allocation); + widget_schedule_resize(tablet.homescreen->widget, + tablet.allocation.width, + tablet.allocation.height); + display_run(display); + + return 0; +} diff --git a/clients/terminal.c b/clients/terminal.c new file mode 100644 index 00000000..cec1d67e --- /dev/null +++ b/clients/terminal.c @@ -0,0 +1,2826 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../shared/config-parser.h" +#include "window.h" + +static int option_fullscreen; +static char *option_font; +static int option_font_size; +static char *option_term; +static char *option_shell; + +static struct wl_list terminal_list; + +static struct terminal * +terminal_create(struct display *display); +static void +terminal_destroy(struct terminal *terminal); +static int +terminal_run(struct terminal *terminal, const char *path); + +#define TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS \ + " !\"#$%&'()*+,-./" \ + "0123456789" \ + ":;<=>?@" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "[\\]^_`" \ + "abcdefghijklmnopqrstuvwxyz" \ + "{|}~" \ + "" + +#define MOD_SHIFT 0x01 +#define MOD_ALT 0x02 +#define MOD_CTRL 0x04 + +#define ATTRMASK_BOLD 0x01 +#define ATTRMASK_UNDERLINE 0x02 +#define ATTRMASK_BLINK 0x04 +#define ATTRMASK_INVERSE 0x08 +#define ATTRMASK_CONCEALED 0x10 + +/* Buffer sizes */ +#define MAX_RESPONSE 256 +#define MAX_ESCAPE 255 + +/* Terminal modes */ +#define MODE_SHOW_CURSOR 0x00000001 +#define MODE_INVERSE 0x00000002 +#define MODE_AUTOWRAP 0x00000004 +#define MODE_AUTOREPEAT 0x00000008 +#define MODE_LF_NEWLINE 0x00000010 +#define MODE_IRM 0x00000020 +#define MODE_DELETE_SENDS_DEL 0x00000040 +#define MODE_ALT_SENDS_ESC 0x00000080 + +union utf8_char { + unsigned char byte[4]; + uint32_t ch; +}; + +enum utf8_state { + utf8state_start, + utf8state_accept, + utf8state_reject, + utf8state_expect3, + utf8state_expect2, + utf8state_expect1 +}; + +struct utf8_state_machine { + enum utf8_state state; + int len; + union utf8_char s; + uint32_t unicode; +}; + +static void +init_state_machine(struct utf8_state_machine *machine) +{ + machine->state = utf8state_start; + machine->len = 0; + machine->s.ch = 0; +} + +static enum utf8_state +utf8_next_char(struct utf8_state_machine *machine, unsigned char c) +{ + switch(machine->state) { + case utf8state_start: + case utf8state_accept: + case utf8state_reject: + machine->s.ch = 0; + machine->len = 0; + if(c == 0xC0 || c == 0xC1) { + /* overlong encoding, reject */ + machine->state = utf8state_reject; + } else if((c & 0x80) == 0) { + /* single byte, accept */ + machine->s.byte[machine->len++] = c; + machine->state = utf8state_accept; + machine->unicode = c; + } else if((c & 0xC0) == 0x80) { + /* parser out of sync, ignore byte */ + machine->state = utf8state_start; + } else if((c & 0xE0) == 0xC0) { + /* start of two byte sequence */ + machine->s.byte[machine->len++] = c; + machine->state = utf8state_expect1; + machine->unicode = c & 0x1f; + } else if((c & 0xF0) == 0xE0) { + /* start of three byte sequence */ + machine->s.byte[machine->len++] = c; + machine->state = utf8state_expect2; + machine->unicode = c & 0x0f; + } else if((c & 0xF8) == 0xF0) { + /* start of four byte sequence */ + machine->s.byte[machine->len++] = c; + machine->state = utf8state_expect3; + machine->unicode = c & 0x07; + } else { + /* overlong encoding, reject */ + machine->state = utf8state_reject; + } + break; + case utf8state_expect3: + machine->s.byte[machine->len++] = c; + machine->unicode = (machine->unicode << 6) | (c & 0x3f); + if((c & 0xC0) == 0x80) { + /* all good, continue */ + machine->state = utf8state_expect2; + } else { + /* missing extra byte, reject */ + machine->state = utf8state_reject; + } + break; + case utf8state_expect2: + machine->s.byte[machine->len++] = c; + machine->unicode = (machine->unicode << 6) | (c & 0x3f); + if((c & 0xC0) == 0x80) { + /* all good, continue */ + machine->state = utf8state_expect1; + } else { + /* missing extra byte, reject */ + machine->state = utf8state_reject; + } + break; + case utf8state_expect1: + machine->s.byte[machine->len++] = c; + machine->unicode = (machine->unicode << 6) | (c & 0x3f); + if((c & 0xC0) == 0x80) { + /* all good, accept */ + machine->state = utf8state_accept; + } else { + /* missing extra byte, reject */ + machine->state = utf8state_reject; + } + break; + default: + machine->state = utf8state_reject; + break; + } + + return machine->state; +} + +static uint32_t +get_unicode(union utf8_char utf8) +{ + struct utf8_state_machine machine; + int i; + + init_state_machine(&machine); + for (i = 0; i < 4; i++) { + utf8_next_char(&machine, utf8.byte[i]); + if (machine.state == utf8state_accept || + machine.state == utf8state_reject) + break; + } + + if (machine.state == utf8state_reject) + return 0xfffd; + + return machine.unicode; +} + +static bool +is_wide(union utf8_char utf8) +{ + uint32_t unichar = get_unicode(utf8); + return wcwidth(unichar) > 1; +} + +struct char_sub { + union utf8_char match; + union utf8_char replace; +}; +/* Set last char_sub match to NULL char */ +typedef struct char_sub *character_set; + +struct char_sub CS_US[] = { + {{{0, }}, {{0, }}} +}; +static struct char_sub CS_UK[] = { + {{{'#', 0, }}, {{0xC2, 0xA3, 0, }}}, /* POUND: £ */ + {{{0, }}, {{0, }}} +}; +static struct char_sub CS_SPECIAL[] = { + {{{'`', 0, }}, {{0xE2, 0x99, 0xA6, 0}}}, /* diamond: ♦ */ + {{{'a', 0, }}, {{0xE2, 0x96, 0x92, 0}}}, /* 50% cell: ▒ */ + {{{'b', 0, }}, {{0xE2, 0x90, 0x89, 0}}}, /* HT: ␉ */ + {{{'c', 0, }}, {{0xE2, 0x90, 0x8C, 0}}}, /* FF: ␌ */ + {{{'d', 0, }}, {{0xE2, 0x90, 0x8D, 0}}}, /* CR: ␍ */ + {{{'e', 0, }}, {{0xE2, 0x90, 0x8A, 0}}}, /* LF: ␊ */ + {{{'f', 0, }}, {{0xC2, 0xB0, 0, }}}, /* Degree: ° */ + {{{'g', 0, }}, {{0xC2, 0xB1, 0, }}}, /* Plus/Minus: ± */ + {{{'h', 0, }}, {{0xE2, 0x90, 0xA4, 0}}}, /* NL: ␤ */ + {{{'i', 0, }}, {{0xE2, 0x90, 0x8B, 0}}}, /* VT: ␋ */ + {{{'j', 0, }}, {{0xE2, 0x94, 0x98, 0}}}, /* CN_RB: ┘ */ + {{{'k', 0, }}, {{0xE2, 0x94, 0x90, 0}}}, /* CN_RT: ┐ */ + {{{'l', 0, }}, {{0xE2, 0x94, 0x8C, 0}}}, /* CN_LT: ┌ */ + {{{'m', 0, }}, {{0xE2, 0x94, 0x94, 0}}}, /* CN_LB: └ */ + {{{'n', 0, }}, {{0xE2, 0x94, 0xBC, 0}}}, /* CROSS: ┼ */ + {{{'o', 0, }}, {{0xE2, 0x8E, 0xBA, 0}}}, /* Horiz. Scan Line 1: ⎺ */ + {{{'p', 0, }}, {{0xE2, 0x8E, 0xBB, 0}}}, /* Horiz. Scan Line 3: ⎻ */ + {{{'q', 0, }}, {{0xE2, 0x94, 0x80, 0}}}, /* Horiz. Scan Line 5: ─ */ + {{{'r', 0, }}, {{0xE2, 0x8E, 0xBC, 0}}}, /* Horiz. Scan Line 7: ⎼ */ + {{{'s', 0, }}, {{0xE2, 0x8E, 0xBD, 0}}}, /* Horiz. Scan Line 9: ⎽ */ + {{{'t', 0, }}, {{0xE2, 0x94, 0x9C, 0}}}, /* TR: ├ */ + {{{'u', 0, }}, {{0xE2, 0x94, 0xA4, 0}}}, /* TL: ┤ */ + {{{'v', 0, }}, {{0xE2, 0x94, 0xB4, 0}}}, /* TU: ┴ */ + {{{'w', 0, }}, {{0xE2, 0x94, 0xAC, 0}}}, /* TD: ┬ */ + {{{'x', 0, }}, {{0xE2, 0x94, 0x82, 0}}}, /* V: │ */ + {{{'y', 0, }}, {{0xE2, 0x89, 0xA4, 0}}}, /* LE: ≤ */ + {{{'z', 0, }}, {{0xE2, 0x89, 0xA5, 0}}}, /* GE: ≥ */ + {{{'{', 0, }}, {{0xCF, 0x80, 0, }}}, /* PI: π */ + {{{'|', 0, }}, {{0xE2, 0x89, 0xA0, 0}}}, /* NEQ: ≠ */ + {{{'}', 0, }}, {{0xC2, 0xA3, 0, }}}, /* POUND: £ */ + {{{'~', 0, }}, {{0xE2, 0x8B, 0x85, 0}}}, /* DOT: ⋅ */ + {{{0, }}, {{0, }}} +}; + +static void +apply_char_set(character_set cs, union utf8_char *utf8) +{ + int i = 0; + + while (cs[i].match.byte[0]) { + if ((*utf8).ch == cs[i].match.ch) { + *utf8 = cs[i].replace; + break; + } + i++; + } +} + +struct key_map { + int sym; + int num; + char escape; + char code; +}; +/* Set last key_sub sym to NULL */ +typedef struct key_map *keyboard_mode; + +static struct key_map KM_NORMAL[] = { + { XKB_KEY_Left, 1, '[', 'D' }, + { XKB_KEY_Right, 1, '[', 'C' }, + { XKB_KEY_Up, 1, '[', 'A' }, + { XKB_KEY_Down, 1, '[', 'B' }, + { XKB_KEY_Home, 1, '[', 'H' }, + { XKB_KEY_End, 1, '[', 'F' }, + { 0, 0, 0, 0 } +}; +static struct key_map KM_APPLICATION[] = { + { XKB_KEY_Left, 1, 'O', 'D' }, + { XKB_KEY_Right, 1, 'O', 'C' }, + { XKB_KEY_Up, 1, 'O', 'A' }, + { XKB_KEY_Down, 1, 'O', 'B' }, + { XKB_KEY_Home, 1, 'O', 'H' }, + { XKB_KEY_End, 1, 'O', 'F' }, + { XKB_KEY_KP_Enter, 1, 'O', 'M' }, + { XKB_KEY_KP_Multiply, 1, 'O', 'j' }, + { XKB_KEY_KP_Add, 1, 'O', 'k' }, + { XKB_KEY_KP_Separator, 1, 'O', 'l' }, + { XKB_KEY_KP_Subtract, 1, 'O', 'm' }, + { XKB_KEY_KP_Divide, 1, 'O', 'o' }, + { 0, 0, 0, 0 } +}; + +static int +function_key_response(char escape, int num, uint32_t modifiers, + char code, char *response) +{ + int mod_num = 0; + int len; + + if (modifiers & MOD_SHIFT_MASK) mod_num |= 1; + if (modifiers & MOD_ALT_MASK) mod_num |= 2; + if (modifiers & MOD_CONTROL_MASK) mod_num |= 4; + + if (mod_num != 0) + len = snprintf(response, MAX_RESPONSE, "\e[%d;%d%c", + num, mod_num + 1, code); + else if (code != '~') + len = snprintf(response, MAX_RESPONSE, "\e%c%c", + escape, code); + else + len = snprintf(response, MAX_RESPONSE, "\e%c%d%c", + escape, num, code); + + if (len >= MAX_RESPONSE) return MAX_RESPONSE - 1; + else return len; +} + +/* returns the number of bytes written into response, + * which must have room for MAX_RESPONSE bytes */ +static int +apply_key_map(keyboard_mode mode, int sym, uint32_t modifiers, char *response) +{ + struct key_map map; + int len = 0; + int i = 0; + + while (mode[i].sym) { + map = mode[i++]; + if (sym == map.sym) { + len = function_key_response(map.escape, map.num, + modifiers, map.code, + response); + break; + } + } + + return len; +} + +struct terminal_color { double r, g, b, a; }; +struct attr { + unsigned char fg, bg; + char a; /* attributes format: + * 76543210 + * cilub */ + char s; /* in selection */ +}; +struct color_scheme { + struct terminal_color palette[16]; + char border; + struct attr default_attr; +}; + +static void +attr_init(struct attr *data_attr, struct attr attr, int n) +{ + int i; + for (i = 0; i < n; i++) { + data_attr[i] = attr; + } +} + +enum escape_state { + escape_state_normal = 0, + escape_state_escape, + escape_state_dcs, + escape_state_csi, + escape_state_osc, + escape_state_inner_escape, + escape_state_ignore, + escape_state_special +}; + +#define ESC_FLAG_WHAT 0x01 +#define ESC_FLAG_GT 0x02 +#define ESC_FLAG_BANG 0x04 +#define ESC_FLAG_CASH 0x08 +#define ESC_FLAG_SQUOTE 0x10 +#define ESC_FLAG_DQUOTE 0x20 +#define ESC_FLAG_SPACE 0x40 + +enum { + SELECT_NONE, + SELECT_CHAR, + SELECT_WORD, + SELECT_LINE +}; + +struct terminal { + struct window *window; + struct widget *widget; + struct display *display; + union utf8_char *data; + struct task io_task; + char *tab_ruler; + struct attr *data_attr; + struct attr curr_attr; + uint32_t mode; + char origin_mode; + char saved_origin_mode; + struct attr saved_attr; + union utf8_char last_char; + int margin_top, margin_bottom; + character_set cs, g0, g1; + character_set saved_cs, saved_g0, saved_g1; + keyboard_mode key_mode; + int data_pitch, attr_pitch; /* The width in bytes of a line */ + int width, height, start, row, column; + int saved_row, saved_column; + int send_cursor_position; + int fd, master; + uint32_t modifiers; + char escape[MAX_ESCAPE+1]; + int escape_length; + enum escape_state state; + enum escape_state outer_state; + int escape_flags; + struct utf8_state_machine state_machine; + int margin; + struct color_scheme *color_scheme; + struct terminal_color color_table[256]; + cairo_font_extents_t extents; + double average_width; + cairo_scaled_font_t *font_normal, *font_bold; + uint32_t hide_cursor_serial; + + struct wl_data_source *selection; + uint32_t button_time; + int dragging, click_count; + int selection_start_x, selection_start_y; + int selection_end_x, selection_end_y; + int selection_start_row, selection_start_col; + int selection_end_row, selection_end_col; + struct wl_list link; +}; + +/* Create default tab stops, every 8 characters */ +static void +terminal_init_tabs(struct terminal *terminal) +{ + int i = 0; + + while (i < terminal->width) { + if (i % 8 == 0) + terminal->tab_ruler[i] = 1; + else + terminal->tab_ruler[i] = 0; + i++; + } +} + +static void +terminal_init(struct terminal *terminal) +{ + terminal->curr_attr = terminal->color_scheme->default_attr; + terminal->origin_mode = 0; + terminal->mode = MODE_SHOW_CURSOR | + MODE_AUTOREPEAT | + MODE_ALT_SENDS_ESC | + MODE_AUTOWRAP; + + terminal->row = 0; + terminal->column = 0; + + terminal->g0 = CS_US; + terminal->g1 = CS_US; + terminal->cs = terminal->g0; + terminal->key_mode = KM_NORMAL; + + terminal->saved_g0 = terminal->g0; + terminal->saved_g1 = terminal->g1; + terminal->saved_cs = terminal->cs; + + terminal->saved_attr = terminal->curr_attr; + terminal->saved_origin_mode = terminal->origin_mode; + terminal->saved_row = terminal->row; + terminal->saved_column = terminal->column; + + if (terminal->tab_ruler != NULL) terminal_init_tabs(terminal); +} + +static void +init_color_table(struct terminal *terminal) +{ + int c, r; + struct terminal_color *color_table = terminal->color_table; + + for (c = 0; c < 256; c ++) { + if (c < 16) { + color_table[c] = terminal->color_scheme->palette[c]; + } else if (c < 232) { + r = c - 16; + color_table[c].b = ((double)(r % 6) / 6.0); r /= 6; + color_table[c].g = ((double)(r % 6) / 6.0); r /= 6; + color_table[c].r = ((double)(r % 6) / 6.0); + color_table[c].a = 1.0; + } else { + r = (c - 232) * 10 + 8; + color_table[c].r = ((double) r) / 256.0; + color_table[c].g = color_table[c].r; + color_table[c].b = color_table[c].r; + color_table[c].a = 1.0; + } + } +} + +static union utf8_char * +terminal_get_row(struct terminal *terminal, int row) +{ + int index; + + index = (row + terminal->start) % terminal->height; + + return &terminal->data[index * terminal->width]; +} + +static struct attr* +terminal_get_attr_row(struct terminal *terminal, int row) +{ + int index; + + index = (row + terminal->start) % terminal->height; + + return &terminal->data_attr[index * terminal->width]; +} + +union decoded_attr { + struct attr attr; + uint32_t key; +}; + +static void +terminal_decode_attr(struct terminal *terminal, int row, int col, + union decoded_attr *decoded) +{ + struct attr attr; + int foreground, background, tmp; + + decoded->attr.s = 0; + if (((row == terminal->selection_start_row && + col >= terminal->selection_start_col) || + row > terminal->selection_start_row) && + ((row == terminal->selection_end_row && + col < terminal->selection_end_col) || + row < terminal->selection_end_row)) + decoded->attr.s = 1; + + /* get the attributes for this character cell */ + attr = terminal_get_attr_row(terminal, row)[col]; + if ((attr.a & ATTRMASK_INVERSE) || + decoded->attr.s || + ((terminal->mode & MODE_SHOW_CURSOR) && + window_has_focus(terminal->window) && terminal->row == row && + terminal->column == col)) { + foreground = attr.bg; + background = attr.fg; + if (attr.a & ATTRMASK_BOLD) { + if (foreground <= 16) foreground |= 0x08; + if (background <= 16) background &= 0x07; + } + } else { + foreground = attr.fg; + background = attr.bg; + } + + if (terminal->mode & MODE_INVERSE) { + tmp = foreground; + foreground = background; + background = tmp; + if (attr.a & ATTRMASK_BOLD) { + if (foreground <= 16) foreground |= 0x08; + if (background <= 16) background &= 0x07; + } + } + + decoded->attr.fg = foreground; + decoded->attr.bg = background; + decoded->attr.a = attr.a; +} + + +static void +terminal_scroll_buffer(struct terminal *terminal, int d) +{ + int i; + + d = d % (terminal->height + 1); + terminal->start = (terminal->start + d) % terminal->height; + if (terminal->start < 0) terminal->start = terminal->height + terminal->start; + if(d < 0) { + d = 0 - d; + for(i = 0; i < d; i++) { + memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); + attr_init(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } else { + for(i = terminal->height - d; i < terminal->height; i++) { + memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); + attr_init(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } + + terminal->selection_start_row -= d; + terminal->selection_end_row -= d; +} + +static void +terminal_scroll_window(struct terminal *terminal, int d) +{ + int i; + int window_height; + int from_row, to_row; + + // scrolling range is inclusive + window_height = terminal->margin_bottom - terminal->margin_top + 1; + d = d % (window_height + 1); + if(d < 0) { + d = 0 - d; + to_row = terminal->margin_bottom; + from_row = terminal->margin_bottom - d; + + for (i = 0; i < (window_height - d); i++) { + memcpy(terminal_get_row(terminal, to_row - i), + terminal_get_row(terminal, from_row - i), + terminal->data_pitch); + memcpy(terminal_get_attr_row(terminal, to_row - i), + terminal_get_attr_row(terminal, from_row - i), + terminal->attr_pitch); + } + for (i = terminal->margin_top; i < (terminal->margin_top + d); i++) { + memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); + attr_init(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } else { + to_row = terminal->margin_top; + from_row = terminal->margin_top + d; + + for (i = 0; i < (window_height - d); i++) { + memcpy(terminal_get_row(terminal, to_row + i), + terminal_get_row(terminal, from_row + i), + terminal->data_pitch); + memcpy(terminal_get_attr_row(terminal, to_row + i), + terminal_get_attr_row(terminal, from_row + i), + terminal->attr_pitch); + } + for (i = terminal->margin_bottom - d + 1; i <= terminal->margin_bottom; i++) { + memset(terminal_get_row(terminal, i), 0, terminal->data_pitch); + attr_init(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } +} + +static void +terminal_scroll(struct terminal *terminal, int d) +{ + if(terminal->margin_top == 0 && terminal->margin_bottom == terminal->height - 1) + terminal_scroll_buffer(terminal, d); + else + terminal_scroll_window(terminal, d); +} + +static void +terminal_shift_line(struct terminal *terminal, int d) +{ + union utf8_char *row; + struct attr *attr_row; + + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + + if ((terminal->width + d) <= terminal->column) + d = terminal->column + 1 - terminal->width; + if ((terminal->column + d) >= terminal->width) + d = terminal->width - terminal->column - 1; + + if (d < 0) { + d = 0 - d; + memmove(&row[terminal->column], + &row[terminal->column + d], + (terminal->width - terminal->column - d) * sizeof(union utf8_char)); + memmove(&attr_row[terminal->column], &attr_row[terminal->column + d], + (terminal->width - terminal->column - d) * sizeof(struct attr)); + memset(&row[terminal->width - d], 0, d * sizeof(union utf8_char)); + attr_init(&attr_row[terminal->width - d], terminal->curr_attr, d); + } else { + memmove(&row[terminal->column + d], &row[terminal->column], + (terminal->width - terminal->column - d) * sizeof(union utf8_char)); + memmove(&attr_row[terminal->column + d], &attr_row[terminal->column], + (terminal->width - terminal->column - d) * sizeof(struct attr)); + memset(&row[terminal->column], 0, d * sizeof(union utf8_char)); + attr_init(&attr_row[terminal->column], terminal->curr_attr, d); + } +} + +static void +terminal_resize_cells(struct terminal *terminal, int width, int height) +{ + size_t size; + union utf8_char *data; + struct attr *data_attr; + char *tab_ruler; + int data_pitch, attr_pitch; + int i, l, total_rows; + struct rectangle allocation; + struct winsize ws; + + if (terminal->width == width && terminal->height == height) + return; + + data_pitch = width * sizeof(union utf8_char); + size = data_pitch * height; + data = zalloc(size); + attr_pitch = width * sizeof(struct attr); + data_attr = malloc(attr_pitch * height); + tab_ruler = zalloc(width); + attr_init(data_attr, terminal->curr_attr, width * height); + if (terminal->data && terminal->data_attr) { + if (width > terminal->width) + l = terminal->width; + else + l = width; + + if (terminal->height > height) { + total_rows = height; + i = 1 + terminal->row - height; + if (i > 0) { + terminal->start = (terminal->start + i) % terminal->height; + terminal->row = terminal->row - i; + } + } else { + total_rows = terminal->height; + } + + for (i = 0; i < total_rows; i++) { + memcpy(&data[width * i], + terminal_get_row(terminal, i), + l * sizeof(union utf8_char)); + memcpy(&data_attr[width * i], + terminal_get_attr_row(terminal, i), + l * sizeof(struct attr)); + } + + free(terminal->data); + free(terminal->data_attr); + free(terminal->tab_ruler); + } + + terminal->data_pitch = data_pitch; + terminal->attr_pitch = attr_pitch; + terminal->margin_bottom = + height - (terminal->height - terminal->margin_bottom); + terminal->width = width; + terminal->height = height; + terminal->data = data; + terminal->data_attr = data_attr; + terminal->tab_ruler = tab_ruler; + terminal->start = 0; + terminal_init_tabs(terminal); + + /* Update the window size */ + ws.ws_row = terminal->height; + ws.ws_col = terminal->width; + widget_get_allocation(terminal->widget, &allocation); + ws.ws_xpixel = allocation.width; + ws.ws_ypixel = allocation.height; + ioctl(terminal->master, TIOCSWINSZ, &ws); +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct terminal *terminal = data; + int32_t columns, rows, m; + + m = 2 * terminal->margin; + columns = (width - m) / (int32_t) terminal->average_width; + rows = (height - m) / (int32_t) terminal->extents.height; + + if (!window_is_fullscreen(terminal->window) && + !window_is_maximized(terminal->window)) { + width = columns * terminal->average_width + m; + height = rows * terminal->extents.height + m; + widget_set_size(terminal->widget, width, height); + } + + terminal_resize_cells(terminal, columns, rows); +} + +static void +terminal_resize(struct terminal *terminal, int columns, int rows) +{ + int32_t width, height, m; + + if (window_is_fullscreen(terminal->window) || + window_is_maximized(terminal->window)) + return; + + m = 2 * terminal->margin; + width = columns * terminal->average_width + m; + height = rows * terminal->extents.height + m; + + frame_set_child_size(terminal->widget, width, height); +} + +struct color_scheme DEFAULT_COLORS = { + { + {0, 0, 0, 1}, /* black */ + {0.66, 0, 0, 1}, /* red */ + {0 , 0.66, 0, 1}, /* green */ + {0.66, 0.33, 0, 1}, /* orange (nicer than muddy yellow) */ + {0 , 0 , 0.66, 1}, /* blue */ + {0.66, 0 , 0.66, 1}, /* magenta */ + {0, 0.66, 0.66, 1}, /* cyan */ + {0.66, 0.66, 0.66, 1}, /* light grey */ + {0.22, 0.33, 0.33, 1}, /* dark grey */ + {1, 0.33, 0.33, 1}, /* high red */ + {0.33, 1, 0.33, 1}, /* high green */ + {1, 1, 0.33, 1}, /* high yellow */ + {0.33, 0.33, 1, 1}, /* high blue */ + {1, 0.33, 1, 1}, /* high magenta */ + {0.33, 1, 1, 1}, /* high cyan */ + {1, 1, 1, 1} /* white */ + }, + 0, /* black border */ + {7, 0, 0, } /* bg:black (0), fg:light gray (7) */ +}; + +static void +terminal_set_color(struct terminal *terminal, cairo_t *cr, int index) +{ + cairo_set_source_rgba(cr, + terminal->color_table[index].r, + terminal->color_table[index].g, + terminal->color_table[index].b, + terminal->color_table[index].a); +} + +static void +terminal_send_selection(struct terminal *terminal, int fd) +{ + int row, col; + union utf8_char *p_row; + union decoded_attr attr; + FILE *fp; + int len; + + fp = fdopen(fd, "w"); + if (fp == NULL){ + close(fd); + return; + } + for (row = 0; row < terminal->height; row++) { + p_row = terminal_get_row(terminal, row); + for (col = 0; col < terminal->width; col++) { + if (p_row[col].ch == 0x200B) /* space glyph */ + continue; + /* get the attributes for this character cell */ + terminal_decode_attr(terminal, row, col, &attr); + if (!attr.attr.s) + continue; + len = strnlen((char *) p_row[col].byte, 4); + if (len > 0) + fwrite(p_row[col].byte, 1, len, fp); + if (len == 0 || col == terminal->width - 1) { + fwrite("\n", 1, 1, fp); + break; + } + } + } + fclose(fp); +} + +struct glyph_run { + struct terminal *terminal; + cairo_t *cr; + unsigned int count; + union decoded_attr attr; + cairo_glyph_t glyphs[256], *g; +}; + +static void +glyph_run_init(struct glyph_run *run, struct terminal *terminal, cairo_t *cr) +{ + run->terminal = terminal; + run->cr = cr; + run->g = run->glyphs; + run->count = 0; + run->attr.key = 0; +} + +static void +glyph_run_flush(struct glyph_run *run, union decoded_attr attr) +{ + cairo_scaled_font_t *font; + + if (run->count > ARRAY_LENGTH(run->glyphs) - 10 || + (attr.key != run->attr.key)) { + if (run->attr.attr.a & (ATTRMASK_BOLD | ATTRMASK_BLINK)) + font = run->terminal->font_bold; + else + font = run->terminal->font_normal; + cairo_set_scaled_font(run->cr, font); + terminal_set_color(run->terminal, run->cr, + run->attr.attr.fg); + + if (!(run->attr.attr.a & ATTRMASK_CONCEALED)) + cairo_show_glyphs (run->cr, run->glyphs, run->count); + run->g = run->glyphs; + run->count = 0; + } + run->attr = attr; +} + +static void +glyph_run_add(struct glyph_run *run, int x, int y, union utf8_char *c) +{ + int num_glyphs; + cairo_scaled_font_t *font; + + num_glyphs = ARRAY_LENGTH(run->glyphs) - run->count; + + if (run->attr.attr.a & (ATTRMASK_BOLD | ATTRMASK_BLINK)) + font = run->terminal->font_bold; + else + font = run->terminal->font_normal; + + cairo_move_to(run->cr, x, y); + cairo_scaled_font_text_to_glyphs (font, x, y, + (char *) c->byte, 4, + &run->g, &num_glyphs, + NULL, NULL, NULL); + run->g += num_glyphs; + run->count += num_glyphs; +} + + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct terminal *terminal = data; + struct rectangle allocation; + cairo_t *cr; + int top_margin, side_margin; + int row, col, cursor_x, cursor_y; + union utf8_char *p_row; + union decoded_attr attr; + int text_x, text_y; + cairo_surface_t *surface; + double d; + struct glyph_run run; + cairo_font_extents_t extents; + double average_width; + double unichar_width; + + surface = window_get_surface(terminal->window); + widget_get_allocation(terminal->widget, &allocation); + cr = widget_cairo_create(terminal->widget); + cairo_rectangle(cr, allocation.x, allocation.y, + allocation.width, allocation.height); + cairo_clip(cr); + cairo_push_group(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + terminal_set_color(terminal, cr, terminal->color_scheme->border); + cairo_paint(cr); + + cairo_set_scaled_font(cr, terminal->font_normal); + + extents = terminal->extents; + average_width = terminal->average_width; + side_margin = (allocation.width - terminal->width * average_width) / 2; + top_margin = (allocation.height - terminal->height * extents.height) / 2; + + cairo_set_line_width(cr, 1.0); + cairo_translate(cr, allocation.x + side_margin, + allocation.y + top_margin); + /* paint the background */ + for (row = 0; row < terminal->height; row++) { + p_row = terminal_get_row(terminal, row); + for (col = 0; col < terminal->width; col++) { + /* get the attributes for this character cell */ + terminal_decode_attr(terminal, row, col, &attr); + + if (attr.attr.bg == terminal->color_scheme->border) + continue; + + if (is_wide(p_row[col])) + unichar_width = 2 * average_width; + else + unichar_width = average_width; + + terminal_set_color(terminal, cr, attr.attr.bg); + cairo_move_to(cr, col * average_width, + row * extents.height); + cairo_rel_line_to(cr, unichar_width, 0); + cairo_rel_line_to(cr, 0, extents.height); + cairo_rel_line_to(cr, -unichar_width, 0); + cairo_close_path(cr); + cairo_fill(cr); + } + } + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + /* paint the foreground */ + glyph_run_init(&run, terminal, cr); + for (row = 0; row < terminal->height; row++) { + p_row = terminal_get_row(terminal, row); + for (col = 0; col < terminal->width; col++) { + /* get the attributes for this character cell */ + terminal_decode_attr(terminal, row, col, &attr); + + glyph_run_flush(&run, attr); + + text_x = col * average_width; + text_y = extents.ascent + row * extents.height; + if (attr.attr.a & ATTRMASK_UNDERLINE) { + terminal_set_color(terminal, cr, attr.attr.fg); + cairo_move_to(cr, text_x, (double)text_y + 1.5); + cairo_line_to(cr, text_x + average_width, (double) text_y + 1.5); + cairo_stroke(cr); + } + + glyph_run_add(&run, text_x, text_y, &p_row[col]); + } + } + + attr.key = ~0; + glyph_run_flush(&run, attr); + + if ((terminal->mode & MODE_SHOW_CURSOR) && + !window_has_focus(terminal->window)) { + d = 0.5; + + cairo_set_line_width(cr, 1); + cairo_move_to(cr, terminal->column * average_width + d, + terminal->row * extents.height + d); + cairo_rel_line_to(cr, average_width - 2 * d, 0); + cairo_rel_line_to(cr, 0, extents.height - 2 * d); + cairo_rel_line_to(cr, -average_width + 2 * d, 0); + cairo_close_path(cr); + + cairo_stroke(cr); + } + + cairo_pop_group_to_source(cr); + cairo_paint(cr); + cairo_destroy(cr); + cairo_surface_destroy(surface); + + if (terminal->send_cursor_position) { + cursor_x = side_margin + allocation.x + + terminal->column * average_width; + cursor_y = top_margin + allocation.y + + terminal->row * extents.height; + window_set_text_cursor_position(terminal->window, + cursor_x, cursor_y); + terminal->send_cursor_position = 0; + } +} + +static void +terminal_write(struct terminal *terminal, const char *data, size_t length) +{ + if (write(terminal->master, data, length) < 0) + abort(); + terminal->send_cursor_position = 1; +} + +static void +terminal_data(struct terminal *terminal, const char *data, size_t length); + +static void +handle_char(struct terminal *terminal, union utf8_char utf8); + +static void +handle_sgr(struct terminal *terminal, int code); + +static void +handle_term_parameter(struct terminal *terminal, int code, int sr) +{ + int i; + + if (terminal->escape_flags & ESC_FLAG_WHAT) { + switch(code) { + case 1: /* DECCKM */ + if (sr) terminal->key_mode = KM_APPLICATION; + else terminal->key_mode = KM_NORMAL; + break; + case 2: /* DECANM */ + /* No VT52 support yet */ + terminal->g0 = CS_US; + terminal->g1 = CS_US; + terminal->cs = terminal->g0; + break; + case 3: /* DECCOLM */ + if (sr) + terminal_resize(terminal, 132, 24); + else + terminal_resize(terminal, 80, 24); + + /* set columns, but also home cursor and clear screen */ + terminal->row = 0; terminal->column = 0; + for (i = 0; i < terminal->height; i++) { + memset(terminal_get_row(terminal, i), + 0, terminal->data_pitch); + attr_init(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + break; + case 5: /* DECSCNM */ + if (sr) terminal->mode |= MODE_INVERSE; + else terminal->mode &= ~MODE_INVERSE; + break; + case 6: /* DECOM */ + terminal->origin_mode = sr; + if (terminal->origin_mode) + terminal->row = terminal->margin_top; + else + terminal->row = 0; + terminal->column = 0; + break; + case 7: /* DECAWM */ + if (sr) terminal->mode |= MODE_AUTOWRAP; + else terminal->mode &= ~MODE_AUTOWRAP; + break; + case 8: /* DECARM */ + if (sr) terminal->mode |= MODE_AUTOREPEAT; + else terminal->mode &= ~MODE_AUTOREPEAT; + break; + case 12: /* Very visible cursor (CVVIS) */ + /* FIXME: What do we do here. */ + break; + case 25: + if (sr) terminal->mode |= MODE_SHOW_CURSOR; + else terminal->mode &= ~MODE_SHOW_CURSOR; + break; + case 1034: /* smm/rmm, meta mode on/off */ + /* ignore */ + break; + case 1037: /* deleteSendsDel */ + if (sr) terminal->mode |= MODE_DELETE_SENDS_DEL; + else terminal->mode &= ~MODE_DELETE_SENDS_DEL; + break; + case 1039: /* altSendsEscape */ + if (sr) terminal->mode |= MODE_ALT_SENDS_ESC; + else terminal->mode &= ~MODE_ALT_SENDS_ESC; + break; + case 1049: /* rmcup/smcup, alternate screen */ + /* Ignore. Should be possible to implement, + * but it's kind of annoying. */ + break; + default: + fprintf(stderr, "Unknown parameter: ?%d\n", code); + break; + } + } else { + switch(code) { + case 4: /* IRM */ + if (sr) terminal->mode |= MODE_IRM; + else terminal->mode &= ~MODE_IRM; + break; + case 20: /* LNM */ + if (sr) terminal->mode |= MODE_LF_NEWLINE; + else terminal->mode &= ~MODE_LF_NEWLINE; + break; + default: + fprintf(stderr, "Unknown parameter: %d\n", code); + break; + } + } +} + +static void +handle_dcs(struct terminal *terminal) +{ +} + +static void +handle_osc(struct terminal *terminal) +{ + char *p; + int code; + + terminal->escape[terminal->escape_length++] = '\0'; + p = &terminal->escape[2]; + code = strtol(p, &p, 10); + if (*p == ';') p++; + + switch (code) { + case 0: /* Icon name and window title */ + case 1: /* Icon label */ + case 2: /* Window title*/ + window_set_title(terminal->window, p); + break; + case 7: /* shell cwd as uri */ + break; + default: + fprintf(stderr, "Unknown OSC escape code %d, text %s\n", + code, p); + break; + } +} + +static void +handle_escape(struct terminal *terminal) +{ + union utf8_char *row; + struct attr *attr_row; + char *p; + int i, count, x, y, top, bottom; + int args[10], set[10] = { 0, }; + char response[MAX_RESPONSE] = {0, }; + struct rectangle allocation; + + terminal->escape[terminal->escape_length++] = '\0'; + i = 0; + p = &terminal->escape[2]; + while ((isdigit(*p) || *p == ';') && i < 10) { + if (*p == ';') { + if (!set[i]) { + args[i] = 0; + set[i] = 1; + } + p++; + i++; + } else { + args[i] = strtol(p, &p, 10); + set[i] = 1; + } + } + + switch (*p) { + case '@': /* ICH */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + terminal_shift_line(terminal, count); + break; + case 'A': /* CUU */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if (terminal->row - count >= terminal->margin_top) + terminal->row -= count; + else + terminal->row = terminal->margin_top; + break; + case 'B': /* CUD */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if (terminal->row + count <= terminal->margin_bottom) + terminal->row += count; + else + terminal->row = terminal->margin_bottom; + break; + case 'C': /* CUF */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if ((terminal->column + count) < terminal->width) + terminal->column += count; + else + terminal->column = terminal->width - 1; + break; + case 'D': /* CUB */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if ((terminal->column - count) >= 0) + terminal->column -= count; + else + terminal->column = 0; + break; + case 'E': /* CNL */ + count = set[0] ? args[0] : 1; + if (terminal->row + count <= terminal->margin_bottom) + terminal->row += count; + else + terminal->row = terminal->margin_bottom; + terminal->column = 0; + break; + case 'F': /* CPL */ + count = set[0] ? args[0] : 1; + if (terminal->row - count >= terminal->margin_top) + terminal->row -= count; + else + terminal->row = terminal->margin_top; + terminal->column = 0; + break; + case 'G': /* CHA */ + y = set[0] ? args[0] : 1; + y = y <= 0 ? 1 : y > terminal->width ? terminal->width : y; + + terminal->column = y - 1; + break; + case 'f': /* HVP */ + case 'H': /* CUP */ + x = (set[1] ? args[1] : 1) - 1; + x = x < 0 ? 0 : + (x >= terminal->width ? terminal->width - 1 : x); + + y = (set[0] ? args[0] : 1) - 1; + if (terminal->origin_mode) { + y += terminal->margin_top; + y = y < terminal->margin_top ? terminal->margin_top : + (y > terminal->margin_bottom ? terminal->margin_bottom : y); + } else { + y = y < 0 ? 0 : + (y >= terminal->height ? terminal->height - 1 : y); + } + + terminal->row = y; + terminal->column = x; + break; + case 'I': /* CHT */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + while (count > 0 && terminal->column < terminal->width) { + if (terminal->tab_ruler[terminal->column]) count--; + terminal->column++; + } + terminal->column--; + break; + case 'J': /* ED */ + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + if (!set[0] || args[0] == 0 || args[0] > 2) { + memset(&row[terminal->column], + 0, (terminal->width - terminal->column) * sizeof(union utf8_char)); + attr_init(&attr_row[terminal->column], + terminal->curr_attr, terminal->width - terminal->column); + for (i = terminal->row + 1; i < terminal->height; i++) { + memset(terminal_get_row(terminal, i), + 0, terminal->data_pitch); + attr_init(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } else if (args[0] == 1) { + memset(row, 0, (terminal->column+1) * sizeof(union utf8_char)); + attr_init(attr_row, terminal->curr_attr, terminal->column+1); + for (i = 0; i < terminal->row; i++) { + memset(terminal_get_row(terminal, i), + 0, terminal->data_pitch); + attr_init(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } else if (args[0] == 2) { + for (i = 0; i < terminal->height; i++) { + memset(terminal_get_row(terminal, i), + 0, terminal->data_pitch); + attr_init(terminal_get_attr_row(terminal, i), + terminal->curr_attr, terminal->width); + } + } + break; + case 'K': /* EL */ + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + if (!set[0] || args[0] == 0 || args[0] > 2) { + memset(&row[terminal->column], 0, + (terminal->width - terminal->column) * sizeof(union utf8_char)); + attr_init(&attr_row[terminal->column], terminal->curr_attr, + terminal->width - terminal->column); + } else if (args[0] == 1) { + memset(row, 0, (terminal->column+1) * sizeof(union utf8_char)); + attr_init(attr_row, terminal->curr_attr, terminal->column+1); + } else if (args[0] == 2) { + memset(row, 0, terminal->data_pitch); + attr_init(attr_row, terminal->curr_attr, terminal->width); + } + break; + case 'L': /* IL */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if (terminal->row >= terminal->margin_top && + terminal->row < terminal->margin_bottom) + { + top = terminal->margin_top; + terminal->margin_top = terminal->row; + terminal_scroll(terminal, 0 - count); + terminal->margin_top = top; + } else if (terminal->row == terminal->margin_bottom) { + memset(terminal_get_row(terminal, terminal->row), + 0, terminal->data_pitch); + attr_init(terminal_get_attr_row(terminal, terminal->row), + terminal->curr_attr, terminal->width); + } + break; + case 'M': /* DL */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if (terminal->row >= terminal->margin_top && + terminal->row < terminal->margin_bottom) + { + top = terminal->margin_top; + terminal->margin_top = terminal->row; + terminal_scroll(terminal, count); + terminal->margin_top = top; + } else if (terminal->row == terminal->margin_bottom) { + memset(terminal_get_row(terminal, terminal->row), + 0, terminal->data_pitch); + } + break; + case 'P': /* DCH */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + terminal_shift_line(terminal, 0 - count); + break; + case 'S': /* SU */ + terminal_scroll(terminal, set[0] ? args[0] : 1); + break; + case 'T': /* SD */ + terminal_scroll(terminal, 0 - (set[0] ? args[0] : 1)); + break; + case 'X': /* ECH */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if ((terminal->column + count) > terminal->width) + count = terminal->width - terminal->column; + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + memset(&row[terminal->column], 0, count * sizeof(union utf8_char)); + attr_init(&attr_row[terminal->column], terminal->curr_attr, count); + break; + case 'Z': /* CBT */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + while (count > 0 && terminal->column >= 0) { + if (terminal->tab_ruler[terminal->column]) count--; + terminal->column--; + } + terminal->column++; + break; + case '`': /* HPA */ + y = set[0] ? args[0] : 1; + y = y <= 0 ? 1 : y > terminal->width ? terminal->width : y; + + terminal->column = y - 1; + break; + case 'b': /* REP */ + count = set[0] ? args[0] : 1; + if (count == 0) count = 1; + if (terminal->last_char.byte[0]) + for (i = 0; i < count; i++) + handle_char(terminal, terminal->last_char); + terminal->last_char.byte[0] = 0; + break; + case 'c': /* Primary DA */ + terminal_write(terminal, "\e[?6c", 5); + break; + case 'd': /* VPA */ + x = set[0] ? args[0] : 1; + x = x <= 0 ? 1 : x > terminal->height ? terminal->height : x; + + terminal->row = x - 1; + break; + case 'g': /* TBC */ + if (!set[0] || args[0] == 0) { + terminal->tab_ruler[terminal->column] = 0; + } else if (args[0] == 3) { + memset(terminal->tab_ruler, 0, terminal->width); + } + break; + case 'h': /* SM */ + for(i = 0; i < 10 && set[i]; i++) { + handle_term_parameter(terminal, args[i], 1); + } + break; + case 'l': /* RM */ + for(i = 0; i < 10 && set[i]; i++) { + handle_term_parameter(terminal, args[i], 0); + } + break; + case 'm': /* SGR */ + for(i = 0; i < 10; i++) { + if (i <= 7 && set[i] && set[i + 1] && + set[i + 2] && args[i + 1] == 5) + { + if (args[i] == 38) { + handle_sgr(terminal, args[i + 2] + 256); + break; + } else if (args[i] == 48) { + handle_sgr(terminal, args[i + 2] + 512); + break; + } + } + if(set[i]) { + handle_sgr(terminal, args[i]); + } else if(i == 0) { + handle_sgr(terminal, 0); + break; + } else { + break; + } + } + break; + case 'n': /* DSR */ + i = set[0] ? args[0] : 0; + if (i == 0 || i == 5) { + terminal_write(terminal, "\e[0n", 4); + } else if (i == 6) { + snprintf(response, MAX_RESPONSE, "\e[%d;%dR", + terminal->origin_mode ? + terminal->row+terminal->margin_top : terminal->row+1, + terminal->column+1); + terminal_write(terminal, response, strlen(response)); + } + break; + case 'r': + if(!set[0]) { + terminal->margin_top = 0; + terminal->margin_bottom = terminal->height-1; + terminal->row = 0; + terminal->column = 0; + } else { + top = (set[0] ? args[0] : 1) - 1; + top = top < 0 ? 0 : + (top >= terminal->height ? terminal->height - 1 : top); + bottom = (set[1] ? args[1] : 1) - 1; + bottom = bottom < 0 ? 0 : + (bottom >= terminal->height ? terminal->height - 1 : bottom); + if(bottom > top) { + terminal->margin_top = top; + terminal->margin_bottom = bottom; + } else { + terminal->margin_top = 0; + terminal->margin_bottom = terminal->height-1; + } + if(terminal->origin_mode) + terminal->row = terminal->margin_top; + else + terminal->row = 0; + terminal->column = 0; + } + break; + case 's': + terminal->saved_row = terminal->row; + terminal->saved_column = terminal->column; + break; + case 't': /* windowOps */ + if (!set[0]) break; + switch (args[0]) { + case 4: /* resize px */ + if (set[1] && set[2]) { + widget_schedule_resize(terminal->widget, + args[2], args[1]); + } + break; + case 8: /* resize ch */ + if (set[1] && set[2]) { + terminal_resize(terminal, args[2], args[1]); + } + break; + case 13: /* report position */ + widget_get_allocation(terminal->widget, &allocation); + snprintf(response, MAX_RESPONSE, "\e[3;%d;%dt", + allocation.x, allocation.y); + terminal_write(terminal, response, strlen(response)); + break; + case 14: /* report px */ + widget_get_allocation(terminal->widget, &allocation); + snprintf(response, MAX_RESPONSE, "\e[4;%d;%dt", + allocation.height, allocation.width); + terminal_write(terminal, response, strlen(response)); + break; + case 18: /* report ch */ + snprintf(response, MAX_RESPONSE, "\e[9;%d;%dt", + terminal->height, terminal->width); + terminal_write(terminal, response, strlen(response)); + break; + case 21: /* report title */ + snprintf(response, MAX_RESPONSE, "\e]l%s\e\\", + window_get_title(terminal->window)); + terminal_write(terminal, response, strlen(response)); + break; + default: + if (args[0] >= 24) + terminal_resize(terminal, terminal->width, args[0]); + else + fprintf(stderr, "Unimplemented windowOp %d\n", args[0]); + break; + } + case 'u': + terminal->row = terminal->saved_row; + terminal->column = terminal->saved_column; + break; + default: + fprintf(stderr, "Unknown CSI escape: %c\n", *p); + break; + } +} + +static void +handle_non_csi_escape(struct terminal *terminal, char code) +{ + switch(code) { + case 'M': /* RI */ + terminal->row -= 1; + if(terminal->row < terminal->margin_top) { + terminal->row = terminal->margin_top; + terminal_scroll(terminal, -1); + } + break; + case 'E': /* NEL */ + terminal->column = 0; + // fallthrough + case 'D': /* IND */ + terminal->row += 1; + if(terminal->row > terminal->margin_bottom) { + terminal->row = terminal->margin_bottom; + terminal_scroll(terminal, +1); + } + break; + case 'c': /* RIS */ + terminal_init(terminal); + break; + case 'H': /* HTS */ + terminal->tab_ruler[terminal->column] = 1; + break; + case '7': /* DECSC */ + terminal->saved_row = terminal->row; + terminal->saved_column = terminal->column; + terminal->saved_attr = terminal->curr_attr; + terminal->saved_origin_mode = terminal->origin_mode; + terminal->saved_cs = terminal->cs; + terminal->saved_g0 = terminal->g0; + terminal->saved_g1 = terminal->g1; + break; + case '8': /* DECRC */ + terminal->row = terminal->saved_row; + terminal->column = terminal->saved_column; + terminal->curr_attr = terminal->saved_attr; + terminal->origin_mode = terminal->saved_origin_mode; + terminal->cs = terminal->saved_cs; + terminal->g0 = terminal->saved_g0; + terminal->g1 = terminal->saved_g1; + break; + case '=': /* DECPAM */ + terminal->key_mode = KM_APPLICATION; + break; + case '>': /* DECPNM */ + terminal->key_mode = KM_NORMAL; + break; + default: + fprintf(stderr, "Unknown escape code: %c\n", code); + break; + } +} + +static void +handle_special_escape(struct terminal *terminal, char special, char code) +{ + int i, numChars; + + if (special == '#') { + switch(code) { + case '8': + /* fill with 'E', no cheap way to do this */ + memset(terminal->data, 0, terminal->data_pitch * terminal->height); + numChars = terminal->width * terminal->height; + for(i = 0; i < numChars; i++) { + terminal->data[i].byte[0] = 'E'; + } + break; + default: + fprintf(stderr, "Unknown HASH escape #%c\n", code); + break; + } + } else if (special == '(' || special == ')') { + switch(code) { + case '0': + if (special == '(') + terminal->g0 = CS_SPECIAL; + else + terminal->g1 = CS_SPECIAL; + break; + case 'A': + if (special == '(') + terminal->g0 = CS_UK; + else + terminal->g1 = CS_UK; + break; + case 'B': + if (special == '(') + terminal->g0 = CS_US; + else + terminal->g1 = CS_US; + break; + default: + fprintf(stderr, "Unknown character set %c\n", code); + break; + } + } else { + fprintf(stderr, "Unknown special escape %c%c\n", special, code); + } +} + +static void +handle_sgr(struct terminal *terminal, int code) +{ + switch(code) { + case 0: + terminal->curr_attr = terminal->color_scheme->default_attr; + break; + case 1: + terminal->curr_attr.a |= ATTRMASK_BOLD; + if (terminal->curr_attr.fg < 8) + terminal->curr_attr.fg += 8; + break; + case 4: + terminal->curr_attr.a |= ATTRMASK_UNDERLINE; + break; + case 5: + terminal->curr_attr.a |= ATTRMASK_BLINK; + break; + case 8: + terminal->curr_attr.a |= ATTRMASK_CONCEALED; + break; + case 2: + case 21: + case 22: + terminal->curr_attr.a &= ~ATTRMASK_BOLD; + if (terminal->curr_attr.fg < 16 && terminal->curr_attr.fg >= 8) + terminal->curr_attr.fg -= 8; + break; + case 24: + terminal->curr_attr.a &= ~ATTRMASK_UNDERLINE; + break; + case 25: + terminal->curr_attr.a &= ~ATTRMASK_BLINK; + break; + case 7: + case 26: + terminal->curr_attr.a |= ATTRMASK_INVERSE; + break; + case 27: + terminal->curr_attr.a &= ~ATTRMASK_INVERSE; + break; + case 28: + terminal->curr_attr.a &= ~ATTRMASK_CONCEALED; + break; + case 39: + terminal->curr_attr.fg = terminal->color_scheme->default_attr.fg; + break; + case 49: + terminal->curr_attr.bg = terminal->color_scheme->default_attr.bg; + break; + default: + if(code >= 30 && code <= 37) { + terminal->curr_attr.fg = code - 30; + if (terminal->curr_attr.a & ATTRMASK_BOLD) + terminal->curr_attr.fg += 8; + } else if(code >= 40 && code <= 47) { + terminal->curr_attr.bg = code - 40; + } else if (code >= 90 && code <= 97) { + terminal->curr_attr.fg = code - 90 + 8; + } else if (code >= 100 && code <= 107) { + terminal->curr_attr.bg = code - 100 + 8; + } else if(code >= 256 && code < 512) { + terminal->curr_attr.fg = code - 256; + } else if(code >= 512 && code < 768) { + terminal->curr_attr.bg = code - 512; + } else { + fprintf(stderr, "Unknown SGR code: %d\n", code); + } + break; + } +} + +/* Returns 1 if c was special, otherwise 0 */ +static int +handle_special_char(struct terminal *terminal, char c) +{ + union utf8_char *row; + struct attr *attr_row; + + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + + switch(c) { + case '\r': + terminal->column = 0; + break; + case '\n': + if (terminal->mode & MODE_LF_NEWLINE) { + terminal->column = 0; + } + /* fallthrough */ + case '\v': + case '\f': + terminal->row++; + if(terminal->row > terminal->margin_bottom) { + terminal->row = terminal->margin_bottom; + terminal_scroll(terminal, +1); + } + + break; + case '\t': + while (terminal->column < terminal->width) { + if (terminal->mode & MODE_IRM) + terminal_shift_line(terminal, +1); + + if (row[terminal->column].byte[0] == '\0') { + row[terminal->column].byte[0] = ' '; + row[terminal->column].byte[1] = '\0'; + attr_row[terminal->column] = terminal->curr_attr; + } + + terminal->column++; + if (terminal->tab_ruler[terminal->column]) break; + } + if (terminal->column >= terminal->width) { + terminal->column = terminal->width - 1; + } + + break; + case '\b': + if (terminal->column >= terminal->width) { + terminal->column = terminal->width - 2; + } else if (terminal->column > 0) { + terminal->column--; + } else if (terminal->mode & MODE_AUTOWRAP) { + terminal->column = terminal->width - 1; + terminal->row -= 1; + if (terminal->row < terminal->margin_top) { + terminal->row = terminal->margin_top; + terminal_scroll(terminal, -1); + } + } + + break; + case '\a': + /* Bell */ + break; + case '\x0E': /* SO */ + terminal->cs = terminal->g1; + break; + case '\x0F': /* SI */ + terminal->cs = terminal->g0; + break; + case '\0': + break; + default: + return 0; + } + + return 1; +} + +static void +handle_char(struct terminal *terminal, union utf8_char utf8) +{ + union utf8_char *row; + struct attr *attr_row; + + if (handle_special_char(terminal, utf8.byte[0])) return; + + apply_char_set(terminal->cs, &utf8); + + /* There are a whole lot of non-characters, control codes, + * and formatting codes that should probably be ignored, + * for example: */ + if (strncmp((char*) utf8.byte, "\xEF\xBB\xBF", 3) == 0) { + /* BOM, ignore */ + return; + } + + /* Some of these non-characters should be translated, e.g.: */ + if (utf8.byte[0] < 32) { + utf8.byte[0] = utf8.byte[0] + 64; + } + + /* handle right margin effects */ + if (terminal->column >= terminal->width) { + if (terminal->mode & MODE_AUTOWRAP) { + terminal->column = 0; + terminal->row += 1; + if (terminal->row > terminal->margin_bottom) { + terminal->row = terminal->margin_bottom; + terminal_scroll(terminal, +1); + } + } else { + terminal->column--; + } + } + + row = terminal_get_row(terminal, terminal->row); + attr_row = terminal_get_attr_row(terminal, terminal->row); + + if (terminal->mode & MODE_IRM) + terminal_shift_line(terminal, +1); + row[terminal->column] = utf8; + attr_row[terminal->column++] = terminal->curr_attr; + + /* cursor jump for wide character. */ + if (is_wide(utf8)) + row[terminal->column++].ch = 0x200B; /* space glyph */ + + if (utf8.ch != terminal->last_char.ch) + terminal->last_char = utf8; +} + +static void +escape_append_utf8(struct terminal *terminal, union utf8_char utf8) +{ + int len, i; + + if ((utf8.byte[0] & 0x80) == 0x00) len = 1; + else if ((utf8.byte[0] & 0xE0) == 0xC0) len = 2; + else if ((utf8.byte[0] & 0xF0) == 0xE0) len = 3; + else if ((utf8.byte[0] & 0xF8) == 0xF0) len = 4; + else len = 1; /* Invalid, cannot happen */ + + if (terminal->escape_length + len <= MAX_ESCAPE) { + for (i = 0; i < len; i++) + terminal->escape[terminal->escape_length + i] = utf8.byte[i]; + terminal->escape_length += len; + } else if (terminal->escape_length < MAX_ESCAPE) { + terminal->escape[terminal->escape_length++] = 0; + } +} + +static void +terminal_data(struct terminal *terminal, const char *data, size_t length) +{ + unsigned int i; + union utf8_char utf8; + enum utf8_state parser_state; + + for (i = 0; i < length; i++) { + parser_state = + utf8_next_char(&terminal->state_machine, data[i]); + switch(parser_state) { + case utf8state_accept: + utf8.ch = terminal->state_machine.s.ch; + break; + case utf8state_reject: + /* the unicode replacement character */ + utf8.byte[0] = 0xEF; + utf8.byte[1] = 0xBF; + utf8.byte[2] = 0xBD; + utf8.byte[3] = 0x00; + break; + default: + continue; + } + + /* assume escape codes never use non-ASCII characters */ + switch (terminal->state) { + case escape_state_escape: + escape_append_utf8(terminal, utf8); + switch (utf8.byte[0]) { + case 'P': /* DCS */ + terminal->state = escape_state_dcs; + break; + case '[': /* CSI */ + terminal->state = escape_state_csi; + break; + case ']': /* OSC */ + terminal->state = escape_state_osc; + break; + case '#': + case '(': + case ')': /* special */ + terminal->state = escape_state_special; + break; + case '^': /* PM (not implemented) */ + case '_': /* APC (not implemented) */ + terminal->state = escape_state_ignore; + break; + default: + terminal->state = escape_state_normal; + handle_non_csi_escape(terminal, utf8.byte[0]); + break; + } + continue; + case escape_state_csi: + if (handle_special_char(terminal, utf8.byte[0]) != 0) { + /* do nothing */ + } else if (utf8.byte[0] == '?') { + terminal->escape_flags |= ESC_FLAG_WHAT; + } else if (utf8.byte[0] == '>') { + terminal->escape_flags |= ESC_FLAG_GT; + } else if (utf8.byte[0] == '!') { + terminal->escape_flags |= ESC_FLAG_BANG; + } else if (utf8.byte[0] == '$') { + terminal->escape_flags |= ESC_FLAG_CASH; + } else if (utf8.byte[0] == '\'') { + terminal->escape_flags |= ESC_FLAG_SQUOTE; + } else if (utf8.byte[0] == '"') { + terminal->escape_flags |= ESC_FLAG_DQUOTE; + } else if (utf8.byte[0] == ' ') { + terminal->escape_flags |= ESC_FLAG_SPACE; + } else { + escape_append_utf8(terminal, utf8); + if (terminal->escape_length >= MAX_ESCAPE) + terminal->state = escape_state_normal; + } + + if (isalpha(utf8.byte[0]) || utf8.byte[0] == '@' || + utf8.byte[0] == '`') + { + terminal->state = escape_state_normal; + handle_escape(terminal); + } else { + } + continue; + case escape_state_inner_escape: + if (utf8.byte[0] == '\\') { + terminal->state = escape_state_normal; + if (terminal->outer_state == escape_state_dcs) { + handle_dcs(terminal); + } else if (terminal->outer_state == escape_state_osc) { + handle_osc(terminal); + } + } else if (utf8.byte[0] == '\e') { + terminal->state = terminal->outer_state; + escape_append_utf8(terminal, utf8); + if (terminal->escape_length >= MAX_ESCAPE) + terminal->state = escape_state_normal; + } else { + terminal->state = terminal->outer_state; + if (terminal->escape_length < MAX_ESCAPE) + terminal->escape[terminal->escape_length++] = '\e'; + escape_append_utf8(terminal, utf8); + if (terminal->escape_length >= MAX_ESCAPE) + terminal->state = escape_state_normal; + } + continue; + case escape_state_dcs: + case escape_state_osc: + case escape_state_ignore: + if (utf8.byte[0] == '\e') { + terminal->outer_state = terminal->state; + terminal->state = escape_state_inner_escape; + } else if (utf8.byte[0] == '\a' && terminal->state == escape_state_osc) { + terminal->state = escape_state_normal; + handle_osc(terminal); + } else { + escape_append_utf8(terminal, utf8); + if (terminal->escape_length >= MAX_ESCAPE) + terminal->state = escape_state_normal; + } + continue; + case escape_state_special: + escape_append_utf8(terminal, utf8); + terminal->state = escape_state_normal; + if (isdigit(utf8.byte[0]) || isalpha(utf8.byte[0])) { + handle_special_escape(terminal, terminal->escape[1], + utf8.byte[0]); + } + continue; + default: + break; + } + + /* this is valid, because ASCII characters are never used to + * introduce a multibyte sequence in UTF-8 */ + if (utf8.byte[0] == '\e') { + terminal->state = escape_state_escape; + terminal->outer_state = escape_state_normal; + terminal->escape[0] = '\e'; + terminal->escape_length = 1; + terminal->escape_flags = 0; + } else { + handle_char(terminal, utf8); + } /* if */ + } /* for */ + + window_schedule_redraw(terminal->window); +} + +static void +data_source_target(void *data, + struct wl_data_source *source, const char *mime_type) +{ + fprintf(stderr, "data_source_target, %s\n", mime_type); +} + +static void +data_source_send(void *data, + struct wl_data_source *source, + const char *mime_type, int32_t fd) +{ + struct terminal *terminal = data; + + terminal_send_selection(terminal, fd); +} + +static void +data_source_cancelled(void *data, struct wl_data_source *source) +{ + wl_data_source_destroy(source); +} + +static const struct wl_data_source_listener data_source_listener = { + data_source_target, + data_source_send, + data_source_cancelled +}; + +static const char text_mime_type[] = "text/plain;charset=utf-8"; + +static void +data_handler(struct window *window, + struct input *input, + float x, float y, const char **types, void *data) +{ + int i, has_text = 0; + + if (!types) + return; + for (i = 0; types[i]; i++) + if (strcmp(types[i], text_mime_type) == 0) + has_text = 1; + + if (!has_text) { + input_accept(input, NULL); + } else { + input_accept(input, text_mime_type); + } +} + +static void +drop_handler(struct window *window, struct input *input, + int32_t x, int32_t y, void *data) +{ + struct terminal *terminal = data; + + input_receive_drag_data_to_fd(input, text_mime_type, terminal->master); +} + +static void +fullscreen_handler(struct window *window, void *data) +{ + struct terminal *terminal = data; + + window_set_fullscreen(window, !window_is_fullscreen(terminal->window)); +} + +static void +close_handler(struct window *window, void *data) +{ + struct terminal *terminal = data; + + terminal_destroy(terminal); +} + +static int +handle_bound_key(struct terminal *terminal, + struct input *input, uint32_t sym, uint32_t time) +{ + struct terminal *new_terminal; + + switch (sym) { + case XKB_KEY_X: + /* Cut selection; terminal doesn't do cut, fall + * through to copy. */ + case XKB_KEY_C: + terminal->selection = + display_create_data_source(terminal->display); + wl_data_source_offer(terminal->selection, + "text/plain;charset=utf-8"); + wl_data_source_add_listener(terminal->selection, + &data_source_listener, terminal); + input_set_selection(input, terminal->selection, + display_get_serial(terminal->display)); + return 1; + case XKB_KEY_V: + input_receive_selection_data_to_fd(input, + "text/plain;charset=utf-8", + terminal->master); + + return 1; + + case XKB_KEY_N: + new_terminal = terminal_create(terminal->display); + if (terminal_run(new_terminal, option_shell)) + terminal_destroy(new_terminal); + + return 1; + + default: + return 0; + } +} + +static void +key_handler(struct window *window, struct input *input, uint32_t time, + uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, + void *data) +{ + struct terminal *terminal = data; + char ch[MAX_RESPONSE]; + uint32_t modifiers, serial; + int ret, len = 0; + bool convert_utf8 = true; + + modifiers = input_get_modifiers(input); + if ((modifiers & MOD_CONTROL_MASK) && + (modifiers & MOD_SHIFT_MASK) && + state == WL_KEYBOARD_KEY_STATE_PRESSED && + handle_bound_key(terminal, input, sym, time)) + return; + + /* Map keypad symbols to 'normal' equivalents before processing */ + switch (sym) { + case XKB_KEY_KP_Space: + sym = XKB_KEY_space; + break; + case XKB_KEY_KP_Tab: + sym = XKB_KEY_Tab; + break; + case XKB_KEY_KP_Enter: + sym = XKB_KEY_Return; + break; + case XKB_KEY_KP_Left: + sym = XKB_KEY_Left; + break; + case XKB_KEY_KP_Up: + sym = XKB_KEY_Up; + break; + case XKB_KEY_KP_Right: + sym = XKB_KEY_Right; + break; + case XKB_KEY_KP_Down: + sym = XKB_KEY_Down; + break; + case XKB_KEY_KP_Equal: + sym = XKB_KEY_equal; + break; + case XKB_KEY_KP_Multiply: + sym = XKB_KEY_asterisk; + break; + case XKB_KEY_KP_Add: + sym = XKB_KEY_plus; + break; + case XKB_KEY_KP_Separator: + /* Note this is actually locale-dependent and should mostly be + * a comma. But leave it as period until we one day start + * doing the right thing. */ + sym = XKB_KEY_period; + break; + case XKB_KEY_KP_Subtract: + sym = XKB_KEY_minus; + break; + case XKB_KEY_KP_Decimal: + sym = XKB_KEY_period; + break; + case XKB_KEY_KP_Divide: + sym = XKB_KEY_slash; + break; + case XKB_KEY_KP_0: + case XKB_KEY_KP_1: + case XKB_KEY_KP_2: + case XKB_KEY_KP_3: + case XKB_KEY_KP_4: + case XKB_KEY_KP_5: + case XKB_KEY_KP_6: + case XKB_KEY_KP_7: + case XKB_KEY_KP_8: + case XKB_KEY_KP_9: + sym = (sym - XKB_KEY_KP_0) + XKB_KEY_0; + break; + default: + break; + } + + switch (sym) { + case XKB_KEY_BackSpace: + if (modifiers & MOD_ALT_MASK) + ch[len++] = 0x1b; + ch[len++] = 0x7f; + break; + case XKB_KEY_Tab: + case XKB_KEY_Linefeed: + case XKB_KEY_Clear: + case XKB_KEY_Pause: + case XKB_KEY_Scroll_Lock: + case XKB_KEY_Sys_Req: + case XKB_KEY_Escape: + ch[len++] = sym & 0x7f; + break; + + case XKB_KEY_Return: + if (terminal->mode & MODE_LF_NEWLINE) { + ch[len++] = 0x0D; + ch[len++] = 0x0A; + } else { + ch[len++] = 0x0D; + } + break; + + case XKB_KEY_Shift_L: + case XKB_KEY_Shift_R: + case XKB_KEY_Control_L: + case XKB_KEY_Control_R: + case XKB_KEY_Alt_L: + case XKB_KEY_Alt_R: + case XKB_KEY_Meta_L: + case XKB_KEY_Meta_R: + case XKB_KEY_Super_L: + case XKB_KEY_Super_R: + case XKB_KEY_Hyper_L: + case XKB_KEY_Hyper_R: + break; + + case XKB_KEY_Insert: + len = function_key_response('[', 2, modifiers, '~', ch); + break; + case XKB_KEY_Delete: + if (terminal->mode & MODE_DELETE_SENDS_DEL) { + ch[len++] = '\x04'; + } else { + len = function_key_response('[', 3, modifiers, '~', ch); + } + break; + case XKB_KEY_Page_Up: + len = function_key_response('[', 5, modifiers, '~', ch); + break; + case XKB_KEY_Page_Down: + len = function_key_response('[', 6, modifiers, '~', ch); + break; + case XKB_KEY_F1: + len = function_key_response('O', 1, modifiers, 'P', ch); + break; + case XKB_KEY_F2: + len = function_key_response('O', 1, modifiers, 'Q', ch); + break; + case XKB_KEY_F3: + len = function_key_response('O', 1, modifiers, 'R', ch); + break; + case XKB_KEY_F4: + len = function_key_response('O', 1, modifiers, 'S', ch); + break; + case XKB_KEY_F5: + len = function_key_response('[', 15, modifiers, '~', ch); + break; + case XKB_KEY_F6: + len = function_key_response('[', 17, modifiers, '~', ch); + break; + case XKB_KEY_F7: + len = function_key_response('[', 18, modifiers, '~', ch); + break; + case XKB_KEY_F8: + len = function_key_response('[', 19, modifiers, '~', ch); + break; + case XKB_KEY_F9: + len = function_key_response('[', 20, modifiers, '~', ch); + break; + case XKB_KEY_F10: + len = function_key_response('[', 21, modifiers, '~', ch); + break; + case XKB_KEY_F12: + len = function_key_response('[', 24, modifiers, '~', ch); + break; + default: + /* Handle special keys with alternate mappings */ + len = apply_key_map(terminal->key_mode, sym, modifiers, ch); + if (len != 0) break; + + if (modifiers & MOD_CONTROL_MASK) { + if (sym >= '3' && sym <= '7') + sym = (sym & 0x1f) + 8; + + if (!((sym >= '!' && sym <= '/') || + (sym >= '8' && sym <= '?') || + (sym >= '0' && sym <= '2'))) sym = sym & 0x1f; + else if (sym == '2') sym = 0x00; + else if (sym == '/') sym = 0x1F; + else if (sym == '8' || sym == '?') sym = 0x7F; + } + if (modifiers & MOD_ALT_MASK) { + if (terminal->mode & MODE_ALT_SENDS_ESC) { + ch[len++] = 0x1b; + } else { + sym = sym | 0x80; + convert_utf8 = false; + } + } + + if ((sym < 128) || + (!convert_utf8 && sym < 256)) { + ch[len++] = sym; + } else { + ret = xkb_keysym_to_utf8(sym, ch + len, + MAX_RESPONSE - len); + if (ret < 0) + fprintf(stderr, + "Warning: buffer too small to encode " + "UTF8 character\n"); + else + len += ret; + } + + break; + } + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED && len > 0) { + terminal_write(terminal, ch, len); + + /* Hide cursor, except if this was coming from a + * repeating key press. */ + serial = display_get_serial(terminal->display); + if (terminal->hide_cursor_serial != serial) { + input_set_pointer_image(input, CURSOR_BLANK); + terminal->hide_cursor_serial = serial; + } + } +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct terminal *terminal = data; + + window_schedule_redraw(terminal->window); +} + +static int wordsep(int ch) +{ + const char extra[] = "-,./?%&#:_=+@~"; + + if (ch > 127) + return 1; + + return ch == 0 || !(isalpha(ch) || isdigit(ch) || strchr(extra, ch)); +} + +static int +recompute_selection(struct terminal *terminal) +{ + struct rectangle allocation; + int col, x, width, height; + int start_row, end_row; + int word_start, eol; + int side_margin, top_margin; + int start_x, end_x; + int cw, ch; + union utf8_char *data; + + cw = terminal->average_width; + ch = terminal->extents.height; + widget_get_allocation(terminal->widget, &allocation); + width = terminal->width * cw; + height = terminal->height * ch; + side_margin = allocation.x + (allocation.width - width) / 2; + top_margin = allocation.y + (allocation.height - height) / 2; + + start_row = (terminal->selection_start_y - top_margin + ch) / ch - 1; + end_row = (terminal->selection_end_y - top_margin + ch) / ch - 1; + + if (start_row < end_row || + (start_row == end_row && + terminal->selection_start_x < terminal->selection_end_x)) { + terminal->selection_start_row = start_row; + terminal->selection_end_row = end_row; + start_x = terminal->selection_start_x; + end_x = terminal->selection_end_x; + } else { + terminal->selection_start_row = end_row; + terminal->selection_end_row = start_row; + start_x = terminal->selection_end_x; + end_x = terminal->selection_start_x; + } + + eol = 0; + if (terminal->selection_start_row < 0) { + terminal->selection_start_row = 0; + terminal->selection_start_col = 0; + } else { + x = side_margin + cw / 2; + data = terminal_get_row(terminal, + terminal->selection_start_row); + word_start = 0; + for (col = 0; col < terminal->width; col++, x += cw) { + if (col == 0 || wordsep(data[col - 1].ch)) + word_start = col; + if (data[col].ch != 0) + eol = col + 1; + if (start_x < x) + break; + } + + switch (terminal->dragging) { + case SELECT_LINE: + terminal->selection_start_col = 0; + break; + case SELECT_WORD: + terminal->selection_start_col = word_start; + break; + case SELECT_CHAR: + terminal->selection_start_col = col; + break; + } + } + + if (terminal->selection_end_row >= terminal->height) { + terminal->selection_end_row = terminal->height; + terminal->selection_end_col = 0; + } else { + x = side_margin + cw / 2; + data = terminal_get_row(terminal, terminal->selection_end_row); + for (col = 0; col < terminal->width; col++, x += cw) { + if (terminal->dragging == SELECT_CHAR && end_x < x) + break; + if (terminal->dragging == SELECT_WORD && + end_x < x && wordsep(data[col].ch)) + break; + } + terminal->selection_end_col = col; + } + + if (terminal->selection_end_col != terminal->selection_start_col || + terminal->selection_start_row != terminal->selection_end_row) { + col = terminal->selection_end_col; + if (col > 0 && data[col - 1].ch == 0) + terminal->selection_end_col = terminal->width; + data = terminal_get_row(terminal, terminal->selection_start_row); + if (data[terminal->selection_start_col].ch == 0) + terminal->selection_start_col = eol; + } + + return 1; +} + +static void +button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct terminal *terminal = data; + + switch (button) { + case 272: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + + if (time - terminal->button_time < 500) + terminal->click_count++; + else + terminal->click_count = 1; + + terminal->button_time = time; + terminal->dragging = + (terminal->click_count - 1) % 3 + SELECT_CHAR; + + input_get_position(input, + &terminal->selection_start_x, + &terminal->selection_start_y); + terminal->selection_end_x = terminal->selection_start_x; + terminal->selection_end_y = terminal->selection_start_y; + if (recompute_selection(terminal)) + widget_schedule_redraw(widget); + } else { + terminal->dragging = SELECT_NONE; + } + break; + } +} + +static int +enter_handler(struct widget *widget, + struct input *input, float x, float y, void *data) +{ + return CURSOR_IBEAM; +} + +static int +motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct terminal *terminal = data; + + if (terminal->dragging) { + input_get_position(input, + &terminal->selection_end_x, + &terminal->selection_end_y); + + if (recompute_selection(terminal)) + widget_schedule_redraw(widget); + } + + return CURSOR_IBEAM; +} + +static void +output_handler(struct window *window, struct output *output, int enter, + void *data) +{ + if (enter) + window_set_buffer_transform(window, output_get_transform(output)); + window_set_buffer_scale(window, window_get_output_scale(window)); + window_schedule_redraw(window); +} + +#ifndef howmany +#define howmany(x, y) (((x) + ((y) - 1)) / (y)) +#endif + +static struct terminal * +terminal_create(struct display *display) +{ + struct terminal *terminal; + cairo_surface_t *surface; + cairo_t *cr; + cairo_text_extents_t text_extents; + + terminal = xzalloc(sizeof *terminal); + terminal->color_scheme = &DEFAULT_COLORS; + terminal_init(terminal); + terminal->margin_top = 0; + terminal->margin_bottom = -1; + terminal->window = window_create(display); + terminal->widget = frame_create(terminal->window, terminal); + window_set_title(terminal->window, "Wayland Terminal"); + widget_set_transparent(terminal->widget, 0); + + init_state_machine(&terminal->state_machine); + init_color_table(terminal); + + terminal->display = display; + terminal->margin = 5; + + window_set_user_data(terminal->window, terminal); + window_set_key_handler(terminal->window, key_handler); + window_set_keyboard_focus_handler(terminal->window, + keyboard_focus_handler); + window_set_fullscreen_handler(terminal->window, fullscreen_handler); + window_set_output_handler(terminal->window, output_handler); + window_set_close_handler(terminal->window, close_handler); + + window_set_data_handler(terminal->window, data_handler); + window_set_drop_handler(terminal->window, drop_handler); + + widget_set_redraw_handler(terminal->widget, redraw_handler); + widget_set_resize_handler(terminal->widget, resize_handler); + widget_set_button_handler(terminal->widget, button_handler); + widget_set_enter_handler(terminal->widget, enter_handler); + widget_set_motion_handler(terminal->widget, motion_handler); + + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); + cr = cairo_create(surface); + cairo_set_font_size(cr, option_font_size); + cairo_select_font_face (cr, option_font, + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + terminal->font_bold = cairo_get_scaled_font (cr); + cairo_scaled_font_reference(terminal->font_bold); + + cairo_select_font_face (cr, option_font, + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + terminal->font_normal = cairo_get_scaled_font (cr); + cairo_scaled_font_reference(terminal->font_normal); + + cairo_font_extents(cr, &terminal->extents); + + /* Compute the average ascii glyph width */ + cairo_text_extents(cr, TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS, + &text_extents); + terminal->average_width = howmany + (text_extents.width, + strlen(TERMINAL_DRAW_SINGLE_WIDE_CHARACTERS)); + terminal->average_width = ceil(terminal->average_width); + + cairo_destroy(cr); + cairo_surface_destroy(surface); + + terminal_resize(terminal, 20, 5); /* Set minimum size first */ + terminal_resize(terminal, 80, 25); + + wl_list_insert(terminal_list.prev, &terminal->link); + + return terminal; +} + +static void +terminal_destroy(struct terminal *terminal) +{ + display_unwatch_fd(terminal->display, terminal->master); + window_destroy(terminal->window); + close(terminal->master); + wl_list_remove(&terminal->link); + + if (wl_list_empty(&terminal_list)) + display_exit(terminal->display); + + free(terminal); +} + +static void +io_handler(struct task *task, uint32_t events) +{ + struct terminal *terminal = + container_of(task, struct terminal, io_task); + char buffer[256]; + int len; + + if (events & EPOLLHUP) { + terminal_destroy(terminal); + return; + } + + len = read(terminal->master, buffer, sizeof buffer); + if (len < 0) + terminal_destroy(terminal); + else + terminal_data(terminal, buffer, len); +} + +static int +terminal_run(struct terminal *terminal, const char *path) +{ + int master; + pid_t pid; + + pid = forkpty(&master, NULL, NULL, NULL); + if (pid == 0) { + setenv("TERM", option_term, 1); + setenv("COLORTERM", option_term, 1); + if (execl(path, path, NULL)) { + printf("exec failed: %m\n"); + exit(EXIT_FAILURE); + } + } else if (pid < 0) { + fprintf(stderr, "failed to fork and create pty (%m).\n"); + return -1; + } + + terminal->master = master; + fcntl(master, F_SETFL, O_NONBLOCK); + terminal->io_task.run = io_handler; + display_watch_fd(terminal->display, terminal->master, + EPOLLIN | EPOLLHUP, &terminal->io_task); + + window_set_fullscreen(terminal->window, option_fullscreen); + if (!window_is_fullscreen(terminal->window)) + terminal_resize(terminal, 80, 24); + + return 0; +} + +static const struct weston_option terminal_options[] = { + { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &option_fullscreen }, + { WESTON_OPTION_STRING, "font", 0, &option_font }, + { WESTON_OPTION_STRING, "shell", 0, &option_shell }, +}; + +int main(int argc, char *argv[]) +{ + struct display *d; + struct terminal *terminal; + struct weston_config *config; + struct weston_config_section *s; + + /* as wcwidth is locale-dependent, + wcwidth needs setlocale call to function properly. */ + setlocale(LC_ALL, ""); + + option_shell = getenv("SHELL"); + if (!option_shell) + option_shell = "/bin/bash"; + + config = weston_config_parse("weston.ini"); + s = weston_config_get_section(config, "terminal", NULL, NULL); + weston_config_section_get_string(s, "font", &option_font, "mono"); + weston_config_section_get_int(s, "font-size", &option_font_size, 14); + weston_config_section_get_string(s, "term", &option_term, "xterm"); + weston_config_destroy(config); + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + wl_list_init(&terminal_list); + terminal = terminal_create(d); + if (terminal_run(terminal, option_shell)) + exit(EXIT_FAILURE); + + display_run(d); + + return 0; +} diff --git a/clients/transformed.c b/clients/transformed.c new file mode 100644 index 00000000..54212dd4 --- /dev/null +++ b/clients/transformed.c @@ -0,0 +1,313 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "window.h" + +struct transformed { + struct display *display; + struct window *window; + struct widget *widget; + int width, height; + int fullscreen; + enum wl_shell_surface_fullscreen_method fullscreen_method; +}; + +static void +draw_stuff(cairo_t *cr, int width, int height) +{ + cairo_matrix_t m; + cairo_get_matrix (cr, &m); + + cairo_translate(cr, width / 2, height / 2); + cairo_scale(cr, width / 2, height / 2); + + cairo_set_source_rgba(cr, 0, 0, 0.3, 1.0); + cairo_set_source_rgba(cr, 0, 0, 0, 1.0); + cairo_rectangle(cr, -1, -1, 2, 2); + cairo_fill(cr); + + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_move_to(cr, 0, 0); + cairo_line_to(cr, 0, -1); + + cairo_save(cr); + cairo_set_matrix(cr, &m); + cairo_set_line_width(cr, 2.0); + cairo_stroke(cr); + cairo_restore(cr); + + cairo_set_source_rgb(cr, 0, 1, 0); + cairo_move_to(cr, 0, 0); + cairo_line_to(cr, 1, 0); + + cairo_save(cr); + cairo_set_matrix(cr, &m); + cairo_set_line_width(cr, 2.0); + cairo_stroke(cr); + cairo_restore(cr); + + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_move_to(cr, 0, 0); + cairo_line_to(cr, 0, 1); + cairo_move_to(cr, 0, 0); + cairo_line_to(cr, -1, 0); + + cairo_save(cr); + cairo_set_matrix(cr, &m); + cairo_set_line_width(cr, 2.0); + cairo_stroke(cr); + cairo_restore(cr); + + cairo_destroy(cr); +} + +static void +fullscreen_handler(struct window *window, void *data) +{ + struct transformed *transformed = data; + + transformed->fullscreen ^= 1; + window_set_fullscreen(window, transformed->fullscreen); +} + +static void +resize_handler(struct widget *widget, int width, int height, void *data) +{ + struct transformed *transformed = data; + + if (transformed->fullscreen_method != + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT) + widget_set_size(widget, transformed->width, transformed->height); +} + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct transformed *transformed = data; + struct rectangle allocation; + cairo_surface_t *surface; + cairo_t *cr; + + surface = window_get_surface(transformed->window); + if (surface == NULL || + cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "failed to create cairo egl surface\n"); + return; + } + + widget_get_allocation(transformed->widget, &allocation); + + cr = widget_cairo_create(widget); + draw_stuff(cr, allocation.width, allocation.height); + + cairo_surface_destroy(surface); +} + +static void +output_handler(struct window *window, struct output *output, int enter, + void *data) +{ + if (!enter) + return; + + window_set_buffer_transform(window, output_get_transform(output)); + window_set_buffer_scale(window, output_get_scale(output)); + window_schedule_redraw(window); +} + +static void +key_handler(struct window *window, struct input *input, uint32_t time, + uint32_t key, uint32_t sym, enum wl_keyboard_key_state state, + void *data) +{ + int transform, scale; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + transform = window_get_buffer_transform (window); + scale = window_get_buffer_scale (window); + switch (sym) { + case XKB_KEY_Left: + if (transform == 0) + transform = 3; + else if (transform == 4) + transform = 7; + else + transform--; + break; + + case XKB_KEY_Right: + if (transform == 3) + transform = 0; + else if (transform == 7) + transform = 4; + else + transform++; + break; + + case XKB_KEY_space: + if (transform >= 4) + transform -= 4; + else + transform += 4; + break; + + case XKB_KEY_z: + if (scale == 1) + scale = 2; + else + scale = 1; + break; + } + + printf ("setting buffer transform to %d\n", transform); + printf ("setting buffer scale to %d\n", scale); + window_set_buffer_transform(window, transform); + window_set_buffer_scale(window, scale); + window_schedule_redraw(window); +} + +static void +button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, enum wl_pointer_button_state state, void *data) +{ + struct transformed *transformed = data; + + switch (button) { + case BTN_LEFT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + window_move(transformed->window, input, + display_get_serial(transformed->display)); + break; + case BTN_MIDDLE: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + widget_schedule_redraw(widget); + break; + case BTN_RIGHT: + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + window_show_frame_menu(transformed->window, input, time); + break; + } +} + +static void +touch_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float x, float y, void *data) +{ + struct transformed *transformed = data; + window_touch_move(transformed->window, input, + display_get_serial(transformed->display)); +} + +static void +usage(int error_code) +{ + fprintf(stderr, "Usage: transformed [OPTIONS]\n\n" + " -d\t\tUse \"driver\" fullscreen method\n" + " -w \tSet window width to \n" + " -h \tSet window height to \n" + " --help\tShow this help text\n\n"); + + exit(error_code); +} + +int main(int argc, char *argv[]) +{ + struct transformed transformed; + struct display *d; + int i; + + transformed.width = 500; + transformed.height = 250; + transformed.fullscreen = 0; + transformed.fullscreen_method = + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-d") == 0) { + transformed.fullscreen_method = + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER; + } else if (strcmp(argv[i], "-w") == 0) { + if (++i >= argc) + usage(EXIT_FAILURE); + + transformed.width = atol(argv[i]); + } else if (strcmp(argv[i], "-h") == 0) { + if (++i >= argc) + usage(EXIT_FAILURE); + + transformed.height = atol(argv[i]); + } else if (strcmp(argv[i], "--help") == 0) + usage(EXIT_SUCCESS); + else + usage(EXIT_FAILURE); + } + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + transformed.display = d; + transformed.window = window_create(d); + transformed.widget = + window_add_widget(transformed.window, &transformed); + + window_set_title(transformed.window, "Transformed"); + window_set_fullscreen_method(transformed.window, + transformed.fullscreen_method); + + widget_set_transparent(transformed.widget, 0); + widget_set_default_cursor(transformed.widget, CURSOR_BLANK); + + widget_set_resize_handler(transformed.widget, resize_handler); + widget_set_redraw_handler(transformed.widget, redraw_handler); + widget_set_button_handler(transformed.widget, button_handler); + + widget_set_touch_down_handler(transformed.widget, touch_handler); + + window_set_key_handler(transformed.window, key_handler); + window_set_fullscreen_handler(transformed.window, fullscreen_handler); + window_set_output_handler(transformed.window, output_handler); + + window_set_user_data(transformed.window, &transformed); + window_schedule_resize(transformed.window, + transformed.width, transformed.height); + + display_run(d); + + return 0; +} diff --git a/clients/view.c b/clients/view.c new file mode 100644 index 00000000..f5b1843f --- /dev/null +++ b/clients/view.c @@ -0,0 +1,314 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2009 Chris Wilson + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "window.h" + +struct view { + struct window *window; + struct widget *widget; + struct display *display; + + PopplerDocument *document; + int page; + int fullscreen; + int *view_counter; +}; + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct view *view = data; + + struct rectangle allocation; + cairo_surface_t *surface; + cairo_t *cr; + PopplerPage *page; + double width, height, doc_aspect, window_aspect, scale; + + widget_get_allocation(view->widget, &allocation); + + surface = window_get_surface(view->window); + + cr = cairo_create(surface); + cairo_rectangle(cr, allocation.x, allocation.y, + allocation.width, allocation.height); + cairo_clip(cr); + + cairo_set_source_rgba(cr, 0, 0, 0, 0.8); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + + if(!view->document) { + cairo_destroy(cr); + cairo_surface_destroy(surface); + return; + } + + page = poppler_document_get_page(view->document, view->page); + poppler_page_get_size(page, &width, &height); + doc_aspect = width / height; + window_aspect = (double) allocation.width / allocation.height; + if (doc_aspect < window_aspect) + scale = allocation.height / height; + else + scale = allocation.width / width; + cairo_translate(cr, allocation.x, allocation.y); + cairo_scale(cr, scale, scale); + cairo_translate(cr, + (allocation.width - width * scale) / 2 / scale, + (allocation.height - height * scale) / 2 / scale); + cairo_rectangle(cr, 0, 0, width, height); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_fill(cr); + poppler_page_render(page, cr); + cairo_destroy(cr); + cairo_surface_destroy(surface); + g_object_unref(G_OBJECT(page)); +} + +static void +resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct view *view = data; + + widget_set_size(view->widget, width, height); +} + +static void +view_page_up(struct view *view) +{ + if(view->page <= 0) + return; + + view->page--; + window_schedule_redraw(view->window); +} + +static void +view_page_down(struct view *view) +{ + if(!view->document || + view->page >= poppler_document_get_n_pages(view->document) - 1) { + return; + } + + view->page++; + window_schedule_redraw(view->window); +} + +static void +button_handler(struct widget *widget, struct input *input, uint32_t time, + uint32_t button, enum wl_pointer_button_state state, + void *data) +{ + struct view *view = data; + + if (state == WL_POINTER_BUTTON_STATE_RELEASED) + return; + + switch(button) { + case 275: + view_page_up(view); + break; + case 276: + view_page_down(view); + break; + default: + break; + } +} + +static void +fullscreen_handler(struct window *window, void *data) +{ + struct view *view = data; + + view->fullscreen ^= 1; + window_set_fullscreen(window, view->fullscreen); +} + +static void +close_handler(struct window *window, void *data) +{ + struct view *view = data; + + *view->view_counter -= 1; + if (*view->view_counter == 0) + display_exit(view->display); + + widget_destroy(view->widget); + window_destroy(view->window); + if (view->document) + g_object_unref(view->document); + + free(view); +} + +static void +key_handler(struct window *window, struct input *input, uint32_t time, + uint32_t key, uint32_t unicode, + enum wl_keyboard_key_state state, void *data) +{ + struct view *view = data; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + switch (key) { + case KEY_SPACE: + case KEY_PAGEDOWN: + case KEY_RIGHT: + case KEY_DOWN: + view_page_down(view); + break; + case KEY_BACKSPACE: + case KEY_PAGEUP: + case KEY_LEFT: + case KEY_UP: + view_page_up(view); + break; + default: + break; + } +} + +static void +keyboard_focus_handler(struct window *window, + struct input *device, void *data) +{ + struct view *view = data; + window_schedule_redraw(view->window); +} + +static struct view * +view_create(struct display *display, + uint32_t key, const char *filename, int fullscreen, int *view_counter) +{ + struct view *view; + gchar *basename; + gchar *title; + GFile *file = NULL; + GError *error = NULL; + + view = zalloc(sizeof *view); + if (view == NULL) + return view; + + file = g_file_new_for_commandline_arg(filename); + basename = g_file_get_basename(file); + if(!basename) { + title = g_strdup("Wayland View"); + } else { + title = g_strdup_printf("Wayland View - %s", basename); + g_free(basename); + } + + view->document = poppler_document_new_from_file(g_file_get_uri(file), + NULL, &error); + + if(error) { + title = g_strdup("File not found"); + } + + view->window = window_create(display); + view->widget = frame_create(view->window, view); + window_set_title(view->window, title); + g_free(title); + view->display = display; + + window_set_user_data(view->window, view); + window_set_key_handler(view->window, key_handler); + window_set_keyboard_focus_handler(view->window, + keyboard_focus_handler); + window_set_fullscreen_handler(view->window, fullscreen_handler); + window_set_close_handler(view->window, close_handler); + + widget_set_button_handler(view->widget, button_handler); + widget_set_resize_handler(view->widget, resize_handler); + widget_set_redraw_handler(view->widget, redraw_handler); + + view->page = 0; + + view->fullscreen = fullscreen; + window_set_fullscreen(view->window, view->fullscreen); + + window_schedule_resize(view->window, 500, 400); + view->view_counter = view_counter; + *view_counter += 1; + + return view; +} + +static int option_fullscreen; + +static const struct weston_option view_options[] = { + { WESTON_OPTION_BOOLEAN, "fullscreen", 0, &option_fullscreen }, +}; + +int +main(int argc, char *argv[]) +{ + struct display *d; + int i; + int view_counter = 0; + +#if !GLIB_CHECK_VERSION(2, 35, 0) + g_type_init(); +#endif + + parse_options(view_options, ARRAY_LENGTH(view_options), &argc, argv); + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + for (i = 1; i < argc; i++) + view_create (d, i, argv[i], option_fullscreen, &view_counter); + + if (view_counter > 0) + display_run(d); + + return 0; +} diff --git a/clients/weston-info.c b/clients/weston-info.c new file mode 100644 index 00000000..5d928f54 --- /dev/null +++ b/clients/weston-info.c @@ -0,0 +1,457 @@ +/* + * Copyright © 2012 Philipp Brüschweiler + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include + +#include "../shared/os-compatibility.h" + +typedef void (*print_info_t)(void *info); + +struct global_info { + struct wl_list link; + + uint32_t id; + uint32_t version; + char *interface; + + print_info_t print; +}; + +struct output_mode { + struct wl_list link; + + uint32_t flags; + int32_t width, height; + int32_t refresh; +}; + +struct output_info { + struct global_info global; + + struct wl_output *output; + + struct { + int32_t x, y; + int32_t physical_width, physical_height; + enum wl_output_subpixel subpixel; + enum wl_output_transform output_transform; + char *make; + char *model; + } geometry; + + struct wl_list modes; +}; + +struct shm_format { + struct wl_list link; + + uint32_t format; +}; + +struct shm_info { + struct global_info global; + struct wl_shm *shm; + + struct wl_list formats; +}; + +struct seat_info { + struct global_info global; + struct wl_seat *seat; + + uint32_t capabilities; + char *name; +}; + +struct weston_info { + struct wl_display *display; + struct wl_registry *registry; + + struct wl_list infos; + bool roundtrip_needed; +}; + +static void * +xmalloc(size_t s) +{ + void *p = malloc(s); + + if (p == NULL) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + + return p; +} + +static void +print_global_info(void *data) +{ + struct global_info *global = data; + + printf("interface: '%s', version: %u, name: %u\n", + global->interface, global->version, global->id); +} + +static void +init_global_info(struct weston_info *info, + struct global_info *global, uint32_t id, + const char *interface, uint32_t version) +{ + global->id = id; + global->version = version; + global->interface = strdup(interface); + + wl_list_insert(info->infos.prev, &global->link); +} + +static void +print_output_info(void *data) +{ + struct output_info *output = data; + struct output_mode *mode; + const char *subpixel_orientation; + const char *transform; + + print_global_info(data); + + switch (output->geometry.subpixel) { + case WL_OUTPUT_SUBPIXEL_UNKNOWN: + subpixel_orientation = "unknown"; + break; + case WL_OUTPUT_SUBPIXEL_NONE: + subpixel_orientation = "none"; + break; + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: + subpixel_orientation = "horizontal rgb"; + break; + case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: + subpixel_orientation = "horizontal bgr"; + break; + case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: + subpixel_orientation = "vertical rgb"; + break; + case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: + subpixel_orientation = "vertical bgr"; + break; + default: + fprintf(stderr, "unknown subpixel orientation %u\n", + output->geometry.subpixel); + subpixel_orientation = "unexpected value"; + break; + } + + switch (output->geometry.output_transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + transform = "normal"; + break; + case WL_OUTPUT_TRANSFORM_90: + transform = "90°"; + break; + case WL_OUTPUT_TRANSFORM_180: + transform = "180°"; + break; + case WL_OUTPUT_TRANSFORM_270: + transform = "270°"; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + transform = "flipped"; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + transform = "flipped 90°"; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + transform = "flipped 180°"; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + transform = "flipped 270°"; + break; + default: + fprintf(stderr, "unknown output transform %u\n", + output->geometry.output_transform); + transform = "unexpected value"; + break; + } + + printf("\tx: %d, y: %d,\n", + output->geometry.x, output->geometry.y); + printf("\tphysical_width: %d mm, physical_height: %d mm,\n", + output->geometry.physical_width, + output->geometry.physical_height); + printf("\tmake: '%s', model: '%s',\n", + output->geometry.make, output->geometry.model); + printf("\tsubpixel_orientation: %s, output_tranform: %s,\n", + subpixel_orientation, transform); + + wl_list_for_each(mode, &output->modes, link) { + printf("\tmode:\n"); + + printf("\t\twidth: %d px, height: %d px, refresh: %.f Hz,\n", + mode->width, mode->height, + (float) mode->refresh / 1000); + + printf("\t\tflags:"); + if (mode->flags & WL_OUTPUT_MODE_CURRENT) + printf(" current"); + if (mode->flags & WL_OUTPUT_MODE_PREFERRED) + printf(" preferred"); + printf("\n"); + } +} + +static void +print_shm_info(void *data) +{ + struct shm_info *shm = data; + struct shm_format *format; + + print_global_info(data); + + printf("\tformats:"); + + wl_list_for_each(format, &shm->formats, link) + printf(" %s", (format->format == WL_SHM_FORMAT_ARGB8888) ? + "ARGB8888" : "XRGB8888"); + + printf("\n"); +} + +static void +print_seat_info(void *data) +{ + struct seat_info *seat = data; + + print_global_info(data); + + printf("\tname: %s\n", seat->name); + printf("\tcapabilities:"); + + if (seat->capabilities & WL_SEAT_CAPABILITY_POINTER) + printf(" pointer"); + if (seat->capabilities & WL_SEAT_CAPABILITY_KEYBOARD) + printf(" keyboard"); + if (seat->capabilities & WL_SEAT_CAPABILITY_TOUCH) + printf(" touch"); + + printf("\n"); +} + +static void +seat_handle_capabilities(void *data, struct wl_seat *wl_seat, + enum wl_seat_capability caps) +{ + struct seat_info *seat = data; + seat->capabilities = caps; +} + +static void +seat_handle_name(void *data, struct wl_seat *wl_seat, + const char *name) +{ + struct seat_info *seat = data; + seat->name = strdup(name); +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, + seat_handle_name, +}; + +static void +add_seat_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct seat_info *seat = xmalloc(sizeof *seat); + + init_global_info(info, &seat->global, id, "wl_seat", version); + seat->global.print = print_seat_info; + + seat->seat = wl_registry_bind(info->registry, + id, &wl_seat_interface, 2); + wl_seat_add_listener(seat->seat, &seat_listener, seat); + + info->roundtrip_needed = true; +} + +static void +shm_handle_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct shm_info *shm = data; + struct shm_format *shm_format = xmalloc(sizeof *shm_format); + + wl_list_insert(&shm->formats, &shm_format->link); + shm_format->format = format; +} + +static const struct wl_shm_listener shm_listener = { + shm_handle_format, +}; + +static void +add_shm_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct shm_info *shm = xmalloc(sizeof *shm); + + init_global_info(info, &shm->global, id, "wl_shm", version); + shm->global.print = print_shm_info; + wl_list_init(&shm->formats); + + shm->shm = wl_registry_bind(info->registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(shm->shm, &shm_listener, shm); + + info->roundtrip_needed = true; +} + +static void +output_handle_geometry(void *data, struct wl_output *wl_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 output_transform) +{ + struct output_info *output = data; + + output->geometry.x = x; + output->geometry.y = y; + output->geometry.physical_width = physical_width; + output->geometry.physical_height = physical_height; + output->geometry.subpixel = subpixel; + output->geometry.make = strdup(make); + output->geometry.model = strdup(model); + output->geometry.output_transform = output_transform; +} + +static void +output_handle_mode(void *data, struct wl_output *wl_output, + uint32_t flags, int32_t width, int32_t height, + int32_t refresh) +{ + struct output_info *output = data; + struct output_mode *mode = xmalloc(sizeof *mode); + + mode->flags = flags; + mode->width = width; + mode->height = height; + mode->refresh = refresh; + + wl_list_insert(output->modes.prev, &mode->link); +} + +static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode, +}; + +static void +add_output_info(struct weston_info *info, uint32_t id, uint32_t version) +{ + struct output_info *output = xmalloc(sizeof *output); + + init_global_info(info, &output->global, id, "wl_output", version); + output->global.print = print_output_info; + + wl_list_init(&output->modes); + + output->output = wl_registry_bind(info->registry, id, + &wl_output_interface, 1); + wl_output_add_listener(output->output, &output_listener, + output); + + info->roundtrip_needed = true; +} + +static void +add_global_info(struct weston_info *info, uint32_t id, + const char *interface, uint32_t version) +{ + struct global_info *global = xmalloc(sizeof *global); + + init_global_info(info, global, id, interface, version); + global->print = print_global_info; +} + +static void +global_handler(void *data, struct wl_registry *registry, uint32_t id, + const char *interface, uint32_t version) +{ + struct weston_info *info = data; + + if (!strcmp(interface, "wl_seat")) + add_seat_info(info, id, version); + else if (!strcmp(interface, "wl_shm")) + add_shm_info(info, id, version); + else if (!strcmp(interface, "wl_output")) + add_output_info(info, id, version); + else + add_global_info(info, id, interface, version); +} + +static void +global_remove_handler(void *data, struct wl_registry *registry, uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + global_handler, + global_remove_handler +}; + +static void +print_infos(struct wl_list *infos) +{ + struct global_info *info; + + wl_list_for_each(info, infos, link) + info->print(info); +} + +int +main(int argc, char **argv) +{ + struct weston_info info; + + info.display = wl_display_connect(NULL); + if (!info.display) { + fprintf(stderr, "failed to create display: %m\n"); + return -1; + } + + wl_list_init(&info.infos); + + info.registry = wl_display_get_registry(info.display); + wl_registry_add_listener(info.registry, ®istry_listener, &info); + + do { + info.roundtrip_needed = false; + wl_display_roundtrip(info.display); + } while (info.roundtrip_needed); + + print_infos(&info.infos); + + return 0; +} diff --git a/clients/weston-simple-im.c b/clients/weston-simple-im.c new file mode 100644 index 00000000..ded6a04d --- /dev/null +++ b/clients/weston-simple-im.c @@ -0,0 +1,518 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include + + +#include + +#include "window.h" +#include "input-method-client-protocol.h" + +enum compose_state { + state_normal, + state_compose +}; + +struct compose_seq { + uint32_t keys[4]; + + const char *text; +}; + +struct simple_im; + +typedef void (*keyboard_input_key_handler_t)(struct simple_im *keyboard, + uint32_t serial, + uint32_t time, uint32_t key, uint32_t unicode, + enum wl_keyboard_key_state state); + +struct simple_im { + struct wl_input_method *input_method; + struct wl_input_method_context *context; + struct wl_display *display; + struct wl_registry *registry; + struct wl_keyboard *keyboard; + enum compose_state compose_state; + struct compose_seq compose_seq; + + struct xkb_context *xkb_context; + + uint32_t modifiers; + + struct xkb_keymap *keymap; + struct xkb_state *state; + xkb_mod_mask_t control_mask; + xkb_mod_mask_t alt_mask; + xkb_mod_mask_t shift_mask; + + keyboard_input_key_handler_t key_handler; + + uint32_t serial; +}; + +static const struct compose_seq compose_seqs[] = { + { { XKB_KEY_quotedbl, XKB_KEY_A, 0 }, "Ä" }, + { { XKB_KEY_quotedbl, XKB_KEY_O, 0 }, "Ö" }, + { { XKB_KEY_quotedbl, XKB_KEY_U, 0 }, "Ü" }, + { { XKB_KEY_quotedbl, XKB_KEY_a, 0 }, "ä" }, + { { XKB_KEY_quotedbl, XKB_KEY_o, 0 }, "ö" }, + { { XKB_KEY_quotedbl, XKB_KEY_u, 0 }, "ü" }, + { { XKB_KEY_apostrophe, XKB_KEY_A, 0 }, "Á" }, + { { XKB_KEY_apostrophe, XKB_KEY_a, 0 }, "á" }, + { { XKB_KEY_slash, XKB_KEY_O, 0 }, "Ø" }, + { { XKB_KEY_slash, XKB_KEY_o, 0 }, "ø" }, + { { XKB_KEY_less, XKB_KEY_3, 0 }, "♥" }, + { { XKB_KEY_A, XKB_KEY_A, 0 }, "Å" }, + { { XKB_KEY_A, XKB_KEY_E, 0 }, "Æ" }, + { { XKB_KEY_O, XKB_KEY_C, 0 }, "©" }, + { { XKB_KEY_O, XKB_KEY_R, 0 }, "®" }, + { { XKB_KEY_s, XKB_KEY_s, 0 }, "ß" }, + { { XKB_KEY_a, XKB_KEY_e, 0 }, "æ" }, + { { XKB_KEY_a, XKB_KEY_a, 0 }, "å" }, +}; + +static const uint32_t ignore_keys_on_compose[] = { + XKB_KEY_Shift_L, + XKB_KEY_Shift_R +}; + +static void +handle_surrounding_text(void *data, + struct wl_input_method_context *context, + const char *text, + uint32_t cursor, + uint32_t anchor) +{ + fprintf(stderr, "Surrounding text updated: %s\n", text); +} + +static void +handle_reset(void *data, + struct wl_input_method_context *context) +{ + struct simple_im *keyboard = data; + + fprintf(stderr, "Reset pre-edit buffer\n"); + + keyboard->compose_state = state_normal; +} + +static void +handle_content_type(void *data, + struct wl_input_method_context *context, + uint32_t hint, + uint32_t purpose) +{ +} + +static void +handle_invoke_action(void *data, + struct wl_input_method_context *context, + uint32_t button, + uint32_t index) +{ +} + +static void +handle_commit_state(void *data, + struct wl_input_method_context *context, + uint32_t serial) +{ + struct simple_im *keyboard = data; + + keyboard->serial = serial; +} + +static void +handle_preferred_language(void *data, + struct wl_input_method_context *context, + const char *language) +{ +} + +static const struct wl_input_method_context_listener input_method_context_listener = { + handle_surrounding_text, + handle_reset, + handle_content_type, + handle_invoke_action, + handle_commit_state, + handle_preferred_language +}; + +static void +input_method_keyboard_keymap(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t format, + int32_t fd, + uint32_t size) +{ + struct simple_im *keyboard = data; + char *map_str; + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + + map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (map_str == MAP_FAILED) { + close(fd); + return; + } + + keyboard->keymap = + xkb_map_new_from_string(keyboard->xkb_context, + map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + 0); + + munmap(map_str, size); + close(fd); + + if (!keyboard->keymap) { + fprintf(stderr, "failed to compile keymap\n"); + return; + } + + keyboard->state = xkb_state_new(keyboard->keymap); + if (!keyboard->state) { + fprintf(stderr, "failed to create XKB state\n"); + xkb_map_unref(keyboard->keymap); + return; + } + + keyboard->control_mask = + 1 << xkb_map_mod_get_index(keyboard->keymap, "Control"); + keyboard->alt_mask = + 1 << xkb_map_mod_get_index(keyboard->keymap, "Mod1"); + keyboard->shift_mask = + 1 << xkb_map_mod_get_index(keyboard->keymap, "Shift"); +} + +static void +input_method_keyboard_key(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state_w) +{ + struct simple_im *keyboard = data; + uint32_t code; + uint32_t num_syms; + const xkb_keysym_t *syms; + xkb_keysym_t sym; + enum wl_keyboard_key_state state = state_w; + + if (!keyboard->state) + return; + + code = key + 8; + num_syms = xkb_key_get_syms(keyboard->state, code, &syms); + + sym = XKB_KEY_NoSymbol; + if (num_syms == 1) + sym = syms[0]; + + if (keyboard->key_handler) + (*keyboard->key_handler)(keyboard, serial, time, key, sym, + state); +} + +static void +input_method_keyboard_modifiers(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) +{ + struct simple_im *keyboard = data; + struct wl_input_method_context *context = keyboard->context; + xkb_mod_mask_t mask; + + xkb_state_update_mask(keyboard->state, mods_depressed, + mods_latched, mods_locked, 0, 0, group); + mask = xkb_state_serialize_mods(keyboard->state, + XKB_STATE_DEPRESSED | + XKB_STATE_LATCHED); + + keyboard->modifiers = 0; + if (mask & keyboard->control_mask) + keyboard->modifiers |= MOD_CONTROL_MASK; + if (mask & keyboard->alt_mask) + keyboard->modifiers |= MOD_ALT_MASK; + if (mask & keyboard->shift_mask) + keyboard->modifiers |= MOD_SHIFT_MASK; + + wl_input_method_context_modifiers(context, serial, + mods_depressed, mods_depressed, + mods_latched, group); +} + +static const struct wl_keyboard_listener input_method_keyboard_listener = { + input_method_keyboard_keymap, + NULL, /* enter */ + NULL, /* leave */ + input_method_keyboard_key, + input_method_keyboard_modifiers +}; + +static void +input_method_activate(void *data, + struct wl_input_method *input_method, + struct wl_input_method_context *context) +{ + struct simple_im *keyboard = data; + + if (keyboard->context) + wl_input_method_context_destroy(keyboard->context); + + keyboard->compose_state = state_normal; + + keyboard->serial = 0; + + keyboard->context = context; + wl_input_method_context_add_listener(context, + &input_method_context_listener, + keyboard); + keyboard->keyboard = wl_input_method_context_grab_keyboard(context); + wl_keyboard_add_listener(keyboard->keyboard, + &input_method_keyboard_listener, + keyboard); +} + +static void +input_method_deactivate(void *data, + struct wl_input_method *input_method, + struct wl_input_method_context *context) +{ + struct simple_im *keyboard = data; + + if (!keyboard->context) + return; + + wl_input_method_context_destroy(keyboard->context); + keyboard->context = NULL; +} + +static const struct wl_input_method_listener input_method_listener = { + input_method_activate, + input_method_deactivate +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, uint32_t version) +{ + struct simple_im *keyboard = data; + + if (!strcmp(interface, "wl_input_method")) { + keyboard->input_method = + wl_registry_bind(registry, name, + &wl_input_method_interface, 1); + wl_input_method_add_listener(keyboard->input_method, + &input_method_listener, keyboard); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static int +compare_compose_keys(const void *c1, const void *c2) +{ + const struct compose_seq *cs1 = c1; + const struct compose_seq *cs2 = c2; + int i; + + for (i = 0; cs1->keys[i] != 0 && cs2->keys[i] != 0; i++) { + if (cs1->keys[i] != cs2->keys[i]) + return cs1->keys[i] - cs2->keys[i]; + } + + if (cs1->keys[i] == cs2->keys[i] + || cs1->keys[i] == 0) + return 0; + + return cs1->keys[i] - cs2->keys[i]; +} + +static void +simple_im_key_handler(struct simple_im *keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t sym, + enum wl_keyboard_key_state state) +{ + struct wl_input_method_context *context = keyboard->context; + char text[64]; + + if (sym == XKB_KEY_Multi_key && + state == WL_KEYBOARD_KEY_STATE_RELEASED && + keyboard->compose_state == state_normal) { + keyboard->compose_state = state_compose; + memset(&keyboard->compose_seq, 0, sizeof(struct compose_seq)); + return; + } + + if (keyboard->compose_state == state_compose) { + uint32_t i = 0; + struct compose_seq *cs; + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + return; + + for (i = 0; i < sizeof(ignore_keys_on_compose) / sizeof(ignore_keys_on_compose[0]); i++) { + if (sym == ignore_keys_on_compose[i]) { + wl_input_method_context_key(context, keyboard->serial, time, key, state); + return; + } + } + + for (i = 0; keyboard->compose_seq.keys[i] != 0; i++); + + keyboard->compose_seq.keys[i] = sym; + + cs = bsearch (&keyboard->compose_seq, compose_seqs, + sizeof(compose_seqs) / sizeof(compose_seqs[0]), + sizeof(compose_seqs[0]), compare_compose_keys); + + if (cs) { + if (cs->keys[i + 1] == 0) { + wl_input_method_context_preedit_cursor(keyboard->context, + 0); + wl_input_method_context_preedit_string(keyboard->context, + keyboard->serial, + "", ""); + wl_input_method_context_cursor_position(keyboard->context, + 0, 0); + wl_input_method_context_commit_string(keyboard->context, + keyboard->serial, + cs->text); + keyboard->compose_state = state_normal; + } else { + uint32_t j = 0, idx = 0; + + for (; j <= i; j++) { + idx += xkb_keysym_to_utf8(cs->keys[j], text + idx, sizeof(text) - idx); + } + + wl_input_method_context_preedit_cursor(keyboard->context, + strlen(text)); + wl_input_method_context_preedit_string(keyboard->context, + keyboard->serial, + text, + text); + } + } else { + uint32_t j = 0, idx = 0; + + for (; j <= i; j++) { + idx += xkb_keysym_to_utf8(keyboard->compose_seq.keys[j], text + idx, sizeof(text) - idx); + } + wl_input_method_context_preedit_cursor(keyboard->context, + 0); + wl_input_method_context_preedit_string(keyboard->context, + keyboard->serial, + "", ""); + wl_input_method_context_cursor_position(keyboard->context, + 0, 0); + wl_input_method_context_commit_string(keyboard->context, + keyboard->serial, + text); + keyboard->compose_state = state_normal; + } + return; + } + + if (xkb_keysym_to_utf8(sym, text, sizeof(text)) <= 0) { + wl_input_method_context_key(context, serial, time, key, state); + return; + } + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + return; + + wl_input_method_context_cursor_position(keyboard->context, + 0, 0); + wl_input_method_context_commit_string(keyboard->context, + keyboard->serial, + text); +} + +int +main(int argc, char *argv[]) +{ + struct simple_im simple_im; + int ret = 0; + + memset(&simple_im, 0, sizeof(simple_im)); + + simple_im.display = wl_display_connect(NULL); + if (simple_im.display == NULL) { + fprintf(stderr, "failed to connect to server: %m\n"); + return -1; + } + + simple_im.registry = wl_display_get_registry(simple_im.display); + wl_registry_add_listener(simple_im.registry, + ®istry_listener, &simple_im); + wl_display_roundtrip(simple_im.display); + if (simple_im.input_method == NULL) { + fprintf(stderr, "No input_method global\n"); + exit(1); + } + + simple_im.xkb_context = xkb_context_new(0); + if (simple_im.xkb_context == NULL) { + fprintf(stderr, "Failed to create XKB context\n"); + return -1; + } + + simple_im.context = NULL; + simple_im.key_handler = simple_im_key_handler; + + while (ret != -1) + ret = wl_display_dispatch(simple_im.display); + + if (ret == -1) { + fprintf(stderr, "Dispatch error: %m\n"); + exit(1); + } + + return 0; +} diff --git a/clients/window.c b/clients/window.c new file mode 100644 index 00000000..56de5d78 --- /dev/null +++ b/clients/window.c @@ -0,0 +1,5658 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2012-2013 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CAIRO_EGL +#include + +#ifdef USE_CAIRO_GLESV2 +#include +#include +#else +#include +#endif +#include +#include + +#include +#else /* HAVE_CAIRO_EGL */ +typedef void *EGLDisplay; +typedef void *EGLConfig; +typedef void *EGLContext; +#define EGL_NO_DISPLAY ((EGLDisplay)0) +#endif /* no HAVE_CAIRO_EGL */ + +#include +#include + +#include +#include +#include "../shared/cairo-util.h" +#include "text-cursor-position-client-protocol.h" +#include "workspaces-client-protocol.h" +#include "../shared/os-compatibility.h" + +#include "window.h" + +struct shm_pool; + +struct global { + uint32_t name; + char *interface; + uint32_t version; + struct wl_list link; +}; + +struct display { + struct wl_display *display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_subcompositor *subcompositor; + struct wl_shell *shell; + struct wl_shm *shm; + struct wl_data_device_manager *data_device_manager; + struct text_cursor_position *text_cursor_position; + struct workspace_manager *workspace_manager; + EGLDisplay dpy; + EGLConfig argb_config; + EGLContext argb_ctx; + cairo_device_t *argb_device; + uint32_t serial; + + int display_fd; + uint32_t display_fd_events; + struct task display_task; + + int epoll_fd; + struct wl_list deferred_list; + + int running; + + struct wl_list global_list; + struct wl_list window_list; + struct wl_list input_list; + struct wl_list output_list; + + struct theme *theme; + + struct wl_cursor_theme *cursor_theme; + struct wl_cursor **cursors; + + display_output_handler_t output_configure_handler; + display_global_handler_t global_handler; + + void *user_data; + + struct xkb_context *xkb_context; + + uint32_t workspace; + uint32_t workspace_count; + + /* A hack to get text extents for tooltips */ + cairo_surface_t *dummy_surface; + void *dummy_surface_data; + + int has_rgb565; + int seat_version; +}; + +enum { + TYPE_NONE, + TYPE_TOPLEVEL, + TYPE_FULLSCREEN, + TYPE_MAXIMIZED, + TYPE_TRANSIENT, + TYPE_MENU, + TYPE_CUSTOM +}; + +struct window_output { + struct output *output; + struct wl_list link; +}; + +struct toysurface { + /* + * Prepare the surface for drawing. Makes sure there is a surface + * of the right size available for rendering, and returns it. + * dx,dy are the x,y of wl_surface.attach. + * width,height are the new buffer size. + * If flags has SURFACE_HINT_RESIZE set, the user is + * doing continuous resizing. + * Returns the Cairo surface to draw to. + */ + cairo_surface_t *(*prepare)(struct toysurface *base, int dx, int dy, + int32_t width, int32_t height, uint32_t flags, + enum wl_output_transform buffer_transform, int32_t buffer_scale); + + /* + * Post the surface to the server, returning the server allocation + * rectangle. The Cairo surface from prepare() must be destroyed + * after calling this. + */ + void (*swap)(struct toysurface *base, + enum wl_output_transform buffer_transform, int32_t buffer_scale, + struct rectangle *server_allocation); + + /* + * Make the toysurface current with the given EGL context. + * Returns 0 on success, and negative of failure. + */ + int (*acquire)(struct toysurface *base, EGLContext ctx); + + /* + * Release the toysurface from the EGL context, returning control + * to Cairo. + */ + void (*release)(struct toysurface *base); + + /* + * Destroy the toysurface, including the Cairo surface, any + * backing storage, and the Wayland protocol objects. + */ + void (*destroy)(struct toysurface *base); +}; + +struct surface { + struct window *window; + + struct wl_surface *surface; + struct wl_subsurface *subsurface; + int synchronized; + int synchronized_default; + struct toysurface *toysurface; + struct widget *widget; + int redraw_needed; + struct wl_callback *frame_cb; + uint32_t last_time; + + struct rectangle allocation; + struct rectangle server_allocation; + + struct wl_region *input_region; + struct wl_region *opaque_region; + + enum window_buffer_type buffer_type; + enum wl_output_transform buffer_transform; + int32_t buffer_scale; + + cairo_surface_t *cairo_surface; + + struct wl_list link; +}; + +struct window { + struct display *display; + struct window *parent; + struct wl_list window_output_list; + char *title; + struct rectangle saved_allocation; + struct rectangle min_allocation; + struct rectangle pending_allocation; + int x, y; + int resize_edges; + int redraw_needed; + int redraw_task_scheduled; + struct task redraw_task; + int resize_needed; + int saved_type; + int type; + int focus_count; + + int resizing; + int fullscreen_method; + int configure_requests; + + enum preferred_format preferred_format; + + window_key_handler_t key_handler; + window_keyboard_focus_handler_t keyboard_focus_handler; + window_data_handler_t data_handler; + window_drop_handler_t drop_handler; + window_close_handler_t close_handler; + window_fullscreen_handler_t fullscreen_handler; + window_output_handler_t output_handler; + + struct surface *main_surface; + struct wl_shell_surface *shell_surface; + + struct frame *frame; + + /* struct surface::link, contains also main_surface */ + struct wl_list subsurface_list; + + void *user_data; + struct wl_list link; +}; + +struct widget { + struct window *window; + struct surface *surface; + struct tooltip *tooltip; + struct wl_list child_list; + struct wl_list link; + struct rectangle allocation; + widget_resize_handler_t resize_handler; + widget_redraw_handler_t redraw_handler; + widget_enter_handler_t enter_handler; + widget_leave_handler_t leave_handler; + widget_motion_handler_t motion_handler; + widget_button_handler_t button_handler; + widget_touch_down_handler_t touch_down_handler; + widget_touch_up_handler_t touch_up_handler; + widget_touch_motion_handler_t touch_motion_handler; + widget_touch_frame_handler_t touch_frame_handler; + widget_touch_cancel_handler_t touch_cancel_handler; + widget_axis_handler_t axis_handler; + void *user_data; + int opaque; + int tooltip_count; + int default_cursor; +}; + +struct touch_point { + int32_t id; + struct widget *widget; + struct wl_list link; +}; + +struct input { + struct display *display; + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; + struct wl_touch *touch; + struct wl_list touch_point_list; + struct window *pointer_focus; + struct window *keyboard_focus; + struct window *touch_focus; + int current_cursor; + uint32_t cursor_anim_start; + struct wl_callback *cursor_frame_cb; + struct wl_surface *pointer_surface; + uint32_t modifiers; + uint32_t pointer_enter_serial; + uint32_t cursor_serial; + float sx, sy; + struct wl_list link; + + struct widget *focus_widget; + struct widget *grab; + uint32_t grab_button; + + struct wl_data_device *data_device; + struct data_offer *drag_offer; + struct data_offer *selection_offer; + + struct { + struct xkb_keymap *keymap; + struct xkb_state *state; + xkb_mod_mask_t control_mask; + xkb_mod_mask_t alt_mask; + xkb_mod_mask_t shift_mask; + } xkb; + + struct task repeat_task; + int repeat_timer_fd; + uint32_t repeat_sym; + uint32_t repeat_key; + uint32_t repeat_time; +}; + +struct output { + struct display *display; + struct wl_output *output; + struct rectangle allocation; + struct wl_list link; + int transform; + int scale; + + display_output_handler_t destroy_handler; + void *user_data; +}; + +enum frame_button_action { + FRAME_BUTTON_NULL = 0, + FRAME_BUTTON_ICON = 1, + FRAME_BUTTON_CLOSE = 2, + FRAME_BUTTON_MINIMIZE = 3, + FRAME_BUTTON_MAXIMIZE = 4, +}; + +enum frame_button_pointer { + FRAME_BUTTON_DEFAULT = 0, + FRAME_BUTTON_OVER = 1, + FRAME_BUTTON_ACTIVE = 2, +}; + +enum frame_button_align { + FRAME_BUTTON_RIGHT = 0, + FRAME_BUTTON_LEFT = 1, +}; + +enum frame_button_decoration { + FRAME_BUTTON_NONE = 0, + FRAME_BUTTON_FANCY = 1, +}; + +struct frame_button { + struct widget *widget; + struct frame *frame; + cairo_surface_t *icon; + enum frame_button_action type; + enum frame_button_pointer state; + struct wl_list link; /* buttons_list */ + enum frame_button_align align; + enum frame_button_decoration decoration; +}; + +struct frame { + struct widget *widget; + struct widget *child; + struct wl_list buttons_list; +}; + +struct menu { + struct window *window; + struct widget *widget; + struct input *input; + const char **entries; + uint32_t time; + int current; + int count; + int release_count; + menu_func_t func; +}; + +struct tooltip { + struct widget *parent; + struct window *window; + struct widget *widget; + char *entry; + struct task tooltip_task; + int tooltip_fd; + float x, y; +}; + +struct shm_pool { + struct wl_shm_pool *pool; + size_t size; + size_t used; + void *data; +}; + +enum { + CURSOR_DEFAULT = 100, + CURSOR_UNSET +}; + +enum window_location { + WINDOW_INTERIOR = 0, + WINDOW_RESIZING_TOP = 1, + WINDOW_RESIZING_BOTTOM = 2, + WINDOW_RESIZING_LEFT = 4, + WINDOW_RESIZING_TOP_LEFT = 5, + WINDOW_RESIZING_BOTTOM_LEFT = 6, + WINDOW_RESIZING_RIGHT = 8, + WINDOW_RESIZING_TOP_RIGHT = 9, + WINDOW_RESIZING_BOTTOM_RIGHT = 10, + WINDOW_RESIZING_MASK = 15, + WINDOW_EXTERIOR = 16, + WINDOW_TITLEBAR = 17, + WINDOW_CLIENT_AREA = 18, +}; + +static const cairo_user_data_key_t shm_surface_data_key; + +/* #define DEBUG */ + +#ifdef DEBUG + +static void +debug_print(void *proxy, int line, const char *func, const char *fmt, ...) +__attribute__ ((format (printf, 4, 5))); + +static void +debug_print(void *proxy, int line, const char *func, const char *fmt, ...) +{ + va_list ap; + struct timeval tv; + + gettimeofday(&tv, NULL); + fprintf(stderr, "%8ld.%03ld ", + (long)tv.tv_sec & 0xffff, (long)tv.tv_usec / 1000); + + if (proxy) + fprintf(stderr, "%s@%d ", + wl_proxy_get_class(proxy), wl_proxy_get_id(proxy)); + + /*fprintf(stderr, __FILE__ ":%d:%s ", line, func);*/ + fprintf(stderr, "%s ", func); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +#define DBG(fmt, ...) \ + debug_print(NULL, __LINE__, __func__, fmt, ##__VA_ARGS__) + +#define DBG_OBJ(obj, fmt, ...) \ + debug_print(obj, __LINE__, __func__, fmt, ##__VA_ARGS__) + +#else + +#define DBG(...) do {} while (0) +#define DBG_OBJ(...) do {} while (0) + +#endif + +static void +surface_to_buffer_size (enum wl_output_transform buffer_transform, int32_t buffer_scale, int32_t *width, int32_t *height) +{ + int32_t tmp; + + switch (buffer_transform) { + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + tmp = *width; + *width = *height; + *height = tmp; + break; + default: + break; + } + + *width *= buffer_scale; + *height *= buffer_scale; +} + +static void +buffer_to_surface_size (enum wl_output_transform buffer_transform, int32_t buffer_scale, int32_t *width, int32_t *height) +{ + int32_t tmp; + + switch (buffer_transform) { + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + tmp = *width; + *width = *height; + *height = tmp; + break; + default: + break; + } + + *width /= buffer_scale; + *height /= buffer_scale; +} + +#ifdef HAVE_CAIRO_EGL + +struct egl_window_surface { + struct toysurface base; + cairo_surface_t *cairo_surface; + struct display *display; + struct wl_surface *surface; + struct wl_egl_window *egl_window; + EGLSurface egl_surface; +}; + +static struct egl_window_surface * +to_egl_window_surface(struct toysurface *base) +{ + return container_of(base, struct egl_window_surface, base); +} + +static cairo_surface_t * +egl_window_surface_prepare(struct toysurface *base, int dx, int dy, + int32_t width, int32_t height, uint32_t flags, + enum wl_output_transform buffer_transform, int32_t buffer_scale) +{ + struct egl_window_surface *surface = to_egl_window_surface(base); + + surface_to_buffer_size (buffer_transform, buffer_scale, &width, &height); + + wl_egl_window_resize(surface->egl_window, width, height, dx, dy); + cairo_gl_surface_set_size(surface->cairo_surface, width, height); + + return cairo_surface_reference(surface->cairo_surface); +} + +static void +egl_window_surface_swap(struct toysurface *base, + enum wl_output_transform buffer_transform, int32_t buffer_scale, + struct rectangle *server_allocation) +{ + struct egl_window_surface *surface = to_egl_window_surface(base); + + cairo_gl_surface_swapbuffers(surface->cairo_surface); + wl_egl_window_get_attached_size(surface->egl_window, + &server_allocation->width, + &server_allocation->height); + + buffer_to_surface_size (buffer_transform, buffer_scale, + &server_allocation->width, + &server_allocation->height); +} + +static int +egl_window_surface_acquire(struct toysurface *base, EGLContext ctx) +{ + struct egl_window_surface *surface = to_egl_window_surface(base); + cairo_device_t *device; + + device = cairo_surface_get_device(surface->cairo_surface); + if (!device) + return -1; + + if (!ctx) { + if (device == surface->display->argb_device) + ctx = surface->display->argb_ctx; + else + assert(0); + } + + cairo_device_flush(device); + cairo_device_acquire(device); + if (!eglMakeCurrent(surface->display->dpy, surface->egl_surface, + surface->egl_surface, ctx)) + fprintf(stderr, "failed to make surface current\n"); + + return 0; +} + +static void +egl_window_surface_release(struct toysurface *base) +{ + struct egl_window_surface *surface = to_egl_window_surface(base); + cairo_device_t *device; + + device = cairo_surface_get_device(surface->cairo_surface); + if (!device) + return; + + if (!eglMakeCurrent(surface->display->dpy, NULL, NULL, + surface->display->argb_ctx)) + fprintf(stderr, "failed to make context current\n"); + + cairo_device_release(device); +} + +static void +egl_window_surface_destroy(struct toysurface *base) +{ + struct egl_window_surface *surface = to_egl_window_surface(base); + struct display *d = surface->display; + + cairo_surface_destroy(surface->cairo_surface); + eglDestroySurface(d->dpy, surface->egl_surface); + wl_egl_window_destroy(surface->egl_window); + surface->surface = NULL; + + free(surface); +} + +static struct toysurface * +egl_window_surface_create(struct display *display, + struct wl_surface *wl_surface, + uint32_t flags, + struct rectangle *rectangle) +{ + struct egl_window_surface *surface; + + if (display->dpy == EGL_NO_DISPLAY) + return NULL; + + surface = calloc(1, sizeof *surface); + if (!surface) + return NULL; + + surface->base.prepare = egl_window_surface_prepare; + surface->base.swap = egl_window_surface_swap; + surface->base.acquire = egl_window_surface_acquire; + surface->base.release = egl_window_surface_release; + surface->base.destroy = egl_window_surface_destroy; + + surface->display = display; + surface->surface = wl_surface; + + surface->egl_window = wl_egl_window_create(surface->surface, + rectangle->width, + rectangle->height); + + surface->egl_surface = eglCreateWindowSurface(display->dpy, + display->argb_config, + surface->egl_window, + NULL); + + surface->cairo_surface = + cairo_gl_surface_create_for_egl(display->argb_device, + surface->egl_surface, + rectangle->width, + rectangle->height); + + return &surface->base; +} + +#else + +static struct toysurface * +egl_window_surface_create(struct display *display, + struct wl_surface *wl_surface, + uint32_t flags, + struct rectangle *rectangle) +{ + return NULL; +} + +#endif + +struct shm_surface_data { + struct wl_buffer *buffer; + struct shm_pool *pool; +}; + +struct wl_buffer * +display_get_buffer_for_surface(struct display *display, + cairo_surface_t *surface) +{ + struct shm_surface_data *data; + + data = cairo_surface_get_user_data(surface, &shm_surface_data_key); + + return data->buffer; +} + +static void +shm_pool_destroy(struct shm_pool *pool); + +static void +shm_surface_data_destroy(void *p) +{ + struct shm_surface_data *data = p; + + wl_buffer_destroy(data->buffer); + if (data->pool) + shm_pool_destroy(data->pool); + + free(data); +} + +static struct wl_shm_pool * +make_shm_pool(struct display *display, int size, void **data) +{ + struct wl_shm_pool *pool; + int fd; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %m\n", + size); + return NULL; + } + + *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (*data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %m\n"); + close(fd); + return NULL; + } + + pool = wl_shm_create_pool(display->shm, fd, size); + + close(fd); + + return pool; +} + +static struct shm_pool * +shm_pool_create(struct display *display, size_t size) +{ + struct shm_pool *pool = malloc(sizeof *pool); + + if (!pool) + return NULL; + + pool->pool = make_shm_pool(display, size, &pool->data); + if (!pool->pool) { + free(pool); + return NULL; + } + + pool->size = size; + pool->used = 0; + + return pool; +} + +static void * +shm_pool_allocate(struct shm_pool *pool, size_t size, int *offset) +{ + if (pool->used + size > pool->size) + return NULL; + + *offset = pool->used; + pool->used += size; + + return (char *) pool->data + *offset; +} + +/* destroy the pool. this does not unmap the memory though */ +static void +shm_pool_destroy(struct shm_pool *pool) +{ + munmap(pool->data, pool->size); + wl_shm_pool_destroy(pool->pool); + free(pool); +} + +/* Start allocating from the beginning of the pool again */ +static void +shm_pool_reset(struct shm_pool *pool) +{ + pool->used = 0; +} + +static int +data_length_for_shm_surface(struct rectangle *rect) +{ + int stride; + + stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, + rect->width); + return stride * rect->height; +} + +static cairo_surface_t * +display_create_shm_surface_from_pool(struct display *display, + struct rectangle *rectangle, + uint32_t flags, struct shm_pool *pool) +{ + struct shm_surface_data *data; + uint32_t format; + cairo_surface_t *surface; + cairo_format_t cairo_format; + int stride, length, offset; + void *map; + + data = malloc(sizeof *data); + if (data == NULL) + return NULL; + + if (flags & SURFACE_HINT_RGB565 && display->has_rgb565) + cairo_format = CAIRO_FORMAT_RGB16_565; + else + cairo_format = CAIRO_FORMAT_ARGB32; + + stride = cairo_format_stride_for_width (cairo_format, rectangle->width); + length = stride * rectangle->height; + data->pool = NULL; + map = shm_pool_allocate(pool, length, &offset); + + if (!map) { + free(data); + return NULL; + } + + surface = cairo_image_surface_create_for_data (map, + cairo_format, + rectangle->width, + rectangle->height, + stride); + + cairo_surface_set_user_data(surface, &shm_surface_data_key, + data, shm_surface_data_destroy); + + if (flags & SURFACE_HINT_RGB565 && display->has_rgb565) + format = WL_SHM_FORMAT_RGB565; + else { + if (flags & SURFACE_OPAQUE) + format = WL_SHM_FORMAT_XRGB8888; + else + format = WL_SHM_FORMAT_ARGB8888; + } + + data->buffer = wl_shm_pool_create_buffer(pool->pool, offset, + rectangle->width, + rectangle->height, + stride, format); + + return surface; +} + +static cairo_surface_t * +display_create_shm_surface(struct display *display, + struct rectangle *rectangle, uint32_t flags, + struct shm_pool *alternate_pool, + struct shm_surface_data **data_ret) +{ + struct shm_surface_data *data; + struct shm_pool *pool; + cairo_surface_t *surface; + + if (alternate_pool) { + shm_pool_reset(alternate_pool); + surface = display_create_shm_surface_from_pool(display, + rectangle, + flags, + alternate_pool); + if (surface) { + data = cairo_surface_get_user_data(surface, + &shm_surface_data_key); + goto out; + } + } + + pool = shm_pool_create(display, + data_length_for_shm_surface(rectangle)); + if (!pool) + return NULL; + + surface = + display_create_shm_surface_from_pool(display, rectangle, + flags, pool); + + if (!surface) { + shm_pool_destroy(pool); + return NULL; + } + + /* make sure we destroy the pool when the surface is destroyed */ + data = cairo_surface_get_user_data(surface, &shm_surface_data_key); + data->pool = pool; + +out: + if (data_ret) + *data_ret = data; + + return surface; +} + +static int +check_size(struct rectangle *rect) +{ + if (rect->width && rect->height) + return 0; + + fprintf(stderr, "tried to create surface of " + "width: %d, height: %d\n", rect->width, rect->height); + return -1; +} + +cairo_surface_t * +display_create_surface(struct display *display, + struct wl_surface *surface, + struct rectangle *rectangle, + uint32_t flags) +{ + if (check_size(rectangle) < 0) + return NULL; + + assert(flags & SURFACE_SHM); + return display_create_shm_surface(display, rectangle, flags, + NULL, NULL); +} + +struct shm_surface_leaf { + cairo_surface_t *cairo_surface; + /* 'data' is automatically destroyed, when 'cairo_surface' is */ + struct shm_surface_data *data; + + struct shm_pool *resize_pool; + int busy; +}; + +static void +shm_surface_leaf_release(struct shm_surface_leaf *leaf) +{ + if (leaf->cairo_surface) + cairo_surface_destroy(leaf->cairo_surface); + /* leaf->data already destroyed via cairo private */ + + if (leaf->resize_pool) + shm_pool_destroy(leaf->resize_pool); + + memset(leaf, 0, sizeof *leaf); +} + +#define MAX_LEAVES 3 + +struct shm_surface { + struct toysurface base; + struct display *display; + struct wl_surface *surface; + uint32_t flags; + int dx, dy; + + struct shm_surface_leaf leaf[MAX_LEAVES]; + struct shm_surface_leaf *current; +}; + +static struct shm_surface * +to_shm_surface(struct toysurface *base) +{ + return container_of(base, struct shm_surface, base); +} + +static void +shm_surface_buffer_state_debug(struct shm_surface *surface, const char *msg) +{ +#ifdef DEBUG + struct shm_surface_leaf *leaf; + char bufs[MAX_LEAVES + 1]; + int i; + + for (i = 0; i < MAX_LEAVES; i++) { + leaf = &surface->leaf[i]; + + if (leaf->busy) + bufs[i] = 'b'; + else if (leaf->cairo_surface) + bufs[i] = 'a'; + else + bufs[i] = ' '; + } + + bufs[MAX_LEAVES] = '\0'; + DBG_OBJ(surface->surface, "%s, leaves [%s]\n", msg, bufs); +#endif +} + +static void +shm_surface_buffer_release(void *data, struct wl_buffer *buffer) +{ + struct shm_surface *surface = data; + struct shm_surface_leaf *leaf; + int i; + int free_found; + + shm_surface_buffer_state_debug(surface, "buffer_release before"); + + for (i = 0; i < MAX_LEAVES; i++) { + leaf = &surface->leaf[i]; + if (leaf->data && leaf->data->buffer == buffer) { + leaf->busy = 0; + break; + } + } + assert(i < MAX_LEAVES && "unknown buffer released"); + + /* Leave one free leaf with storage, release others */ + free_found = 0; + for (i = 0; i < MAX_LEAVES; i++) { + leaf = &surface->leaf[i]; + + if (!leaf->cairo_surface || leaf->busy) + continue; + + if (!free_found) + free_found = 1; + else + shm_surface_leaf_release(leaf); + } + + shm_surface_buffer_state_debug(surface, "buffer_release after"); +} + +static const struct wl_buffer_listener shm_surface_buffer_listener = { + shm_surface_buffer_release +}; + +static cairo_surface_t * +shm_surface_prepare(struct toysurface *base, int dx, int dy, + int32_t width, int32_t height, uint32_t flags, + enum wl_output_transform buffer_transform, int32_t buffer_scale) +{ + int resize_hint = !!(flags & SURFACE_HINT_RESIZE); + struct shm_surface *surface = to_shm_surface(base); + struct rectangle rect = { 0}; + struct shm_surface_leaf *leaf = NULL; + int i; + + surface->dx = dx; + surface->dy = dy; + + /* pick a free buffer, preferrably one that already has storage */ + for (i = 0; i < MAX_LEAVES; i++) { + if (surface->leaf[i].busy) + continue; + + if (!leaf || surface->leaf[i].cairo_surface) + leaf = &surface->leaf[i]; + } + DBG_OBJ(surface->surface, "pick leaf %d\n", + (int)(leaf - &surface->leaf[0])); + + if (!leaf) { + fprintf(stderr, "%s: all buffers are held by the server.\n", + __func__); + exit(1); + return NULL; + } + + if (!resize_hint && leaf->resize_pool) { + cairo_surface_destroy(leaf->cairo_surface); + leaf->cairo_surface = NULL; + shm_pool_destroy(leaf->resize_pool); + leaf->resize_pool = NULL; + } + + surface_to_buffer_size (buffer_transform, buffer_scale, &width, &height); + + if (leaf->cairo_surface && + cairo_image_surface_get_width(leaf->cairo_surface) == width && + cairo_image_surface_get_height(leaf->cairo_surface) == height) + goto out; + + if (leaf->cairo_surface) + cairo_surface_destroy(leaf->cairo_surface); + +#ifdef USE_RESIZE_POOL + if (resize_hint && !leaf->resize_pool) { + /* Create a big pool to allocate from, while continuously + * resizing. Mmapping a new pool in the server + * is relatively expensive, so reusing a pool performs + * better, but may temporarily reserve unneeded memory. + */ + /* We should probably base this number on the output size. */ + leaf->resize_pool = shm_pool_create(surface->display, + 6 * 1024 * 1024); + } +#endif + + rect.width = width; + rect.height = height; + + leaf->cairo_surface = + display_create_shm_surface(surface->display, &rect, + surface->flags, + leaf->resize_pool, + &leaf->data); + wl_buffer_add_listener(leaf->data->buffer, + &shm_surface_buffer_listener, surface); + +out: + surface->current = leaf; + + return cairo_surface_reference(leaf->cairo_surface); +} + +static void +shm_surface_swap(struct toysurface *base, + enum wl_output_transform buffer_transform, int32_t buffer_scale, + struct rectangle *server_allocation) +{ + struct shm_surface *surface = to_shm_surface(base); + struct shm_surface_leaf *leaf = surface->current; + + server_allocation->width = + cairo_image_surface_get_width(leaf->cairo_surface); + server_allocation->height = + cairo_image_surface_get_height(leaf->cairo_surface); + + buffer_to_surface_size (buffer_transform, buffer_scale, + &server_allocation->width, + &server_allocation->height); + + wl_surface_attach(surface->surface, leaf->data->buffer, + surface->dx, surface->dy); + wl_surface_damage(surface->surface, 0, 0, + server_allocation->width, server_allocation->height); + wl_surface_commit(surface->surface); + + DBG_OBJ(surface->surface, "leaf %d busy\n", + (int)(leaf - &surface->leaf[0])); + + leaf->busy = 1; + surface->current = NULL; +} + +static int +shm_surface_acquire(struct toysurface *base, EGLContext ctx) +{ + return -1; +} + +static void +shm_surface_release(struct toysurface *base) +{ +} + +static void +shm_surface_destroy(struct toysurface *base) +{ + struct shm_surface *surface = to_shm_surface(base); + int i; + + for (i = 0; i < MAX_LEAVES; i++) + shm_surface_leaf_release(&surface->leaf[i]); + + free(surface); +} + +static struct toysurface * +shm_surface_create(struct display *display, struct wl_surface *wl_surface, + uint32_t flags, struct rectangle *rectangle) +{ + struct shm_surface *surface; + DBG_OBJ(wl_surface, "\n"); + + surface = xmalloc(sizeof *surface); + memset(surface, 0, sizeof *surface); + + if (!surface) + return NULL; + + surface->base.prepare = shm_surface_prepare; + surface->base.swap = shm_surface_swap; + surface->base.acquire = shm_surface_acquire; + surface->base.release = shm_surface_release; + surface->base.destroy = shm_surface_destroy; + + surface->display = display; + surface->surface = wl_surface; + surface->flags = flags; + + return &surface->base; +} + +/* + * The following correspondences between file names and cursors was copied + * from: https://bugs.kde.org/attachment.cgi?id=67313 + */ + +static const char *bottom_left_corners[] = { + "bottom_left_corner", + "sw-resize", + "size_bdiag" +}; + +static const char *bottom_right_corners[] = { + "bottom_right_corner", + "se-resize", + "size_fdiag" +}; + +static const char *bottom_sides[] = { + "bottom_side", + "s-resize", + "size_ver" +}; + +static const char *grabbings[] = { + "grabbing", + "closedhand", + "208530c400c041818281048008011002" +}; + +static const char *left_ptrs[] = { + "left_ptr", + "default", + "top_left_arrow", + "left-arrow" +}; + +static const char *left_sides[] = { + "left_side", + "w-resize", + "size_hor" +}; + +static const char *right_sides[] = { + "right_side", + "e-resize", + "size_hor" +}; + +static const char *top_left_corners[] = { + "top_left_corner", + "nw-resize", + "size_fdiag" +}; + +static const char *top_right_corners[] = { + "top_right_corner", + "ne-resize", + "size_bdiag" +}; + +static const char *top_sides[] = { + "top_side", + "n-resize", + "size_ver" +}; + +static const char *xterms[] = { + "xterm", + "ibeam", + "text" +}; + +static const char *hand1s[] = { + "hand1", + "pointer", + "pointing_hand", + "e29285e634086352946a0e7090d73106" +}; + +static const char *watches[] = { + "watch", + "wait", + "0426c94ea35c87780ff01dc239897213" +}; + +struct cursor_alternatives { + const char **names; + size_t count; +}; + +static const struct cursor_alternatives cursors[] = { + {bottom_left_corners, ARRAY_LENGTH(bottom_left_corners)}, + {bottom_right_corners, ARRAY_LENGTH(bottom_right_corners)}, + {bottom_sides, ARRAY_LENGTH(bottom_sides)}, + {grabbings, ARRAY_LENGTH(grabbings)}, + {left_ptrs, ARRAY_LENGTH(left_ptrs)}, + {left_sides, ARRAY_LENGTH(left_sides)}, + {right_sides, ARRAY_LENGTH(right_sides)}, + {top_left_corners, ARRAY_LENGTH(top_left_corners)}, + {top_right_corners, ARRAY_LENGTH(top_right_corners)}, + {top_sides, ARRAY_LENGTH(top_sides)}, + {xterms, ARRAY_LENGTH(xterms)}, + {hand1s, ARRAY_LENGTH(hand1s)}, + {watches, ARRAY_LENGTH(watches)}, +}; + +static void +create_cursors(struct display *display) +{ + struct weston_config *config; + struct weston_config_section *s; + int size; + char *theme = NULL; + unsigned int i, j; + struct wl_cursor *cursor; + + config = weston_config_parse("weston.ini"); + s = weston_config_get_section(config, "shell", NULL, NULL); + weston_config_section_get_string(s, "cursor-theme", &theme, NULL); + weston_config_section_get_int(s, "cursor-size", &size, 32); + weston_config_destroy(config); + + display->cursor_theme = wl_cursor_theme_load(theme, size, display->shm); + free(theme); + display->cursors = + xmalloc(ARRAY_LENGTH(cursors) * sizeof display->cursors[0]); + + for (i = 0; i < ARRAY_LENGTH(cursors); i++) { + cursor = NULL; + for (j = 0; !cursor && j < cursors[i].count; ++j) + cursor = wl_cursor_theme_get_cursor( + display->cursor_theme, cursors[i].names[j]); + + if (!cursor) + fprintf(stderr, "could not load cursor '%s'\n", + cursors[i].names[0]); + + display->cursors[i] = cursor; + } +} + +static void +destroy_cursors(struct display *display) +{ + wl_cursor_theme_destroy(display->cursor_theme); + free(display->cursors); +} + +struct wl_cursor_image * +display_get_pointer_image(struct display *display, int pointer) +{ + struct wl_cursor *cursor = display->cursors[pointer]; + + return cursor ? cursor->images[0] : NULL; +} + +static void +surface_flush(struct surface *surface) +{ + if (!surface->cairo_surface) + return; + + if (surface->opaque_region) { + wl_surface_set_opaque_region(surface->surface, + surface->opaque_region); + wl_region_destroy(surface->opaque_region); + surface->opaque_region = NULL; + } + + if (surface->input_region) { + wl_surface_set_input_region(surface->surface, + surface->input_region); + wl_region_destroy(surface->input_region); + surface->input_region = NULL; + } + + surface->toysurface->swap(surface->toysurface, + surface->buffer_transform, surface->buffer_scale, + &surface->server_allocation); + + cairo_surface_destroy(surface->cairo_surface); + surface->cairo_surface = NULL; +} + +int +window_has_focus(struct window *window) +{ + return window->focus_count > 0; +} + +static void +window_flush(struct window *window) +{ + struct surface *surface; + + if (window->type == TYPE_NONE) { + window->type = TYPE_TOPLEVEL; + if (window->shell_surface) + wl_shell_surface_set_toplevel(window->shell_surface); + } + + wl_list_for_each(surface, &window->subsurface_list, link) { + if (surface == window->main_surface) + continue; + + surface_flush(surface); + } + + surface_flush(window->main_surface); +} + +struct display * +window_get_display(struct window *window) +{ + return window->display; +} + +static void +surface_create_surface(struct surface *surface, int dx, int dy, uint32_t flags) +{ + struct display *display = surface->window->display; + struct rectangle allocation = surface->allocation; + + if (!surface->toysurface && display->dpy && + surface->buffer_type == WINDOW_BUFFER_TYPE_EGL_WINDOW) { + surface->toysurface = + egl_window_surface_create(display, + surface->surface, + flags, + &allocation); + } + + if (!surface->toysurface) + surface->toysurface = shm_surface_create(display, + surface->surface, + flags, &allocation); + + surface->cairo_surface = surface->toysurface->prepare( + surface->toysurface, dx, dy, + allocation.width, allocation.height, flags, + surface->buffer_transform, surface->buffer_scale); +} + +static void +window_create_main_surface(struct window *window) +{ + struct surface *surface = window->main_surface; + uint32_t flags = 0; + int dx = 0; + int dy = 0; + + if (window->resizing) + flags |= SURFACE_HINT_RESIZE; + + if (window->preferred_format == WINDOW_PREFERRED_FORMAT_RGB565) + flags |= SURFACE_HINT_RGB565; + + if (window->resize_edges & WINDOW_RESIZING_LEFT) + dx = surface->server_allocation.width - + surface->allocation.width; + + if (window->resize_edges & WINDOW_RESIZING_TOP) + dy = surface->server_allocation.height - + surface->allocation.height; + + window->resize_edges = 0; + + surface_create_surface(surface, dx, dy, flags); +} + +int +window_get_buffer_transform(struct window *window) +{ + return window->main_surface->buffer_transform; +} + +void +window_set_buffer_transform(struct window *window, + enum wl_output_transform transform) +{ + window->main_surface->buffer_transform = transform; + wl_surface_set_buffer_transform(window->main_surface->surface, + transform); +} + +void +window_set_buffer_scale(struct window *window, + int32_t scale) +{ + window->main_surface->buffer_scale = scale; + wl_surface_set_buffer_scale(window->main_surface->surface, + scale); +} + +uint32_t +window_get_buffer_scale(struct window *window) +{ + return window->main_surface->buffer_scale; +} + +uint32_t +window_get_output_scale(struct window *window) +{ + struct window_output *window_output; + struct window_output *window_output_tmp; + int scale = 1; + + wl_list_for_each_safe(window_output, window_output_tmp, + &window->window_output_list, link) { + if (window_output->output->scale > scale) + scale = window_output->output->scale; + } + + return scale; +} + +static void frame_destroy(struct frame *frame); + +static void +surface_destroy(struct surface *surface) +{ + if (surface->frame_cb) + wl_callback_destroy(surface->frame_cb); + + if (surface->input_region) + wl_region_destroy(surface->input_region); + + if (surface->opaque_region) + wl_region_destroy(surface->opaque_region); + + if (surface->subsurface) + wl_subsurface_destroy(surface->subsurface); + + wl_surface_destroy(surface->surface); + + if (surface->toysurface) + surface->toysurface->destroy(surface->toysurface); + + wl_list_remove(&surface->link); + free(surface); +} + +void +window_destroy(struct window *window) +{ + struct display *display = window->display; + struct input *input; + struct window_output *window_output; + struct window_output *window_output_tmp; + + wl_list_remove(&window->redraw_task.link); + + wl_list_for_each(input, &display->input_list, link) { + if (input->touch_focus == window) + input->touch_focus = NULL; + if (input->pointer_focus == window) + input->pointer_focus = NULL; + if (input->keyboard_focus == window) + input->keyboard_focus = NULL; + if (input->focus_widget && + input->focus_widget->window == window) + input->focus_widget = NULL; + } + + wl_list_for_each_safe(window_output, window_output_tmp, + &window->window_output_list, link) { + free (window_output); + } + + if (window->frame) + frame_destroy(window->frame); + + if (window->shell_surface) + wl_shell_surface_destroy(window->shell_surface); + + surface_destroy(window->main_surface); + + wl_list_remove(&window->link); + + free(window->title); + free(window); +} + +static struct widget * +widget_find_widget(struct widget *widget, int32_t x, int32_t y) +{ + struct widget *child, *target; + + wl_list_for_each(child, &widget->child_list, link) { + target = widget_find_widget(child, x, y); + if (target) + return target; + } + + if (widget->allocation.x <= x && + x < widget->allocation.x + widget->allocation.width && + widget->allocation.y <= y && + y < widget->allocation.y + widget->allocation.height) { + return widget; + } + + return NULL; +} + +static struct widget * +window_find_widget(struct window *window, int32_t x, int32_t y) +{ + struct surface *surface; + struct widget *widget; + + wl_list_for_each(surface, &window->subsurface_list, link) { + widget = widget_find_widget(surface->widget, x, y); + if (widget) + return widget; + } + + return NULL; +} + +static struct widget * +widget_create(struct window *window, struct surface *surface, void *data) +{ + struct widget *widget; + + widget = xzalloc(sizeof *widget); + widget->window = window; + widget->surface = surface; + widget->user_data = data; + widget->allocation = surface->allocation; + wl_list_init(&widget->child_list); + widget->opaque = 0; + widget->tooltip = NULL; + widget->tooltip_count = 0; + widget->default_cursor = CURSOR_LEFT_PTR; + + return widget; +} + +struct widget * +window_add_widget(struct window *window, void *data) +{ + struct widget *widget; + + widget = widget_create(window, window->main_surface, data); + wl_list_init(&widget->link); + window->main_surface->widget = widget; + + return widget; +} + +struct widget * +widget_add_widget(struct widget *parent, void *data) +{ + struct widget *widget; + + widget = widget_create(parent->window, parent->surface, data); + wl_list_insert(parent->child_list.prev, &widget->link); + + return widget; +} + +void +widget_destroy(struct widget *widget) +{ + struct display *display = widget->window->display; + struct surface *surface = widget->surface; + struct input *input; + + /* Destroy the sub-surface along with the root widget */ + if (surface->widget == widget && surface->subsurface) + surface_destroy(widget->surface); + + if (widget->tooltip) { + free(widget->tooltip); + widget->tooltip = NULL; + } + + wl_list_for_each(input, &display->input_list, link) { + if (input->focus_widget == widget) + input->focus_widget = NULL; + } + + wl_list_remove(&widget->link); + free(widget); +} + +void +widget_set_default_cursor(struct widget *widget, int cursor) +{ + widget->default_cursor = cursor; +} + +void +widget_get_allocation(struct widget *widget, struct rectangle *allocation) +{ + *allocation = widget->allocation; +} + +void +widget_set_size(struct widget *widget, int32_t width, int32_t height) +{ + widget->allocation.width = width; + widget->allocation.height = height; +} + +void +widget_set_allocation(struct widget *widget, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + widget->allocation.x = x; + widget->allocation.y = y; + widget_set_size(widget, width, height); +} + +void +widget_set_transparent(struct widget *widget, int transparent) +{ + widget->opaque = !transparent; +} + +void * +widget_get_user_data(struct widget *widget) +{ + return widget->user_data; +} + +static cairo_surface_t * +widget_get_cairo_surface(struct widget *widget) +{ + struct surface *surface = widget->surface; + struct window *window = widget->window; + + if (!surface->cairo_surface) { + if (surface == window->main_surface) + window_create_main_surface(window); + else + surface_create_surface(surface, 0, 0, 0); + } + + return surface->cairo_surface; +} + +static void +widget_cairo_update_transform(struct widget *widget, cairo_t *cr) +{ + struct surface *surface = widget->surface; + double angle; + cairo_matrix_t m; + enum wl_output_transform transform; + int surface_width, surface_height; + int translate_x, translate_y; + int32_t scale; + + surface_width = surface->allocation.width; + surface_height = surface->allocation.height; + + transform = surface->buffer_transform; + scale = surface->buffer_scale; + + switch (transform) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + cairo_matrix_init(&m, -1, 0, 0, 1, 0, 0); + break; + default: + cairo_matrix_init_identity(&m); + break; + } + + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + default: + angle = 0; + translate_x = 0; + translate_y = 0; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + angle = 0; + translate_x = surface_width; + translate_y = 0; + break; + case WL_OUTPUT_TRANSFORM_90: + angle = M_PI_2; + translate_x = surface_height; + translate_y = 0; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + angle = M_PI_2; + translate_x = surface_height; + translate_y = surface_width; + break; + case WL_OUTPUT_TRANSFORM_180: + angle = M_PI; + translate_x = surface_width; + translate_y = surface_height; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + angle = M_PI; + translate_x = 0; + translate_y = surface_height; + break; + case WL_OUTPUT_TRANSFORM_270: + angle = M_PI + M_PI_2; + translate_x = 0; + translate_y = surface_width; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + angle = M_PI + M_PI_2; + translate_x = 0; + translate_y = 0; + break; + } + + cairo_scale(cr, scale, scale); + cairo_translate(cr, translate_x, translate_y); + cairo_rotate(cr, angle); + cairo_transform(cr, &m); +} + +cairo_t * +widget_cairo_create(struct widget *widget) +{ + struct surface *surface = widget->surface; + cairo_surface_t *cairo_surface; + cairo_t *cr; + + cairo_surface = widget_get_cairo_surface(widget); + cr = cairo_create(cairo_surface); + + widget_cairo_update_transform(widget, cr); + + cairo_translate(cr, -surface->allocation.x, -surface->allocation.y); + + return cr; +} + +struct wl_surface * +widget_get_wl_surface(struct widget *widget) +{ + return widget->surface->surface; +} + +uint32_t +widget_get_last_time(struct widget *widget) +{ + return widget->surface->last_time; +} + +void +widget_input_region_add(struct widget *widget, const struct rectangle *rect) +{ + struct wl_compositor *comp = widget->window->display->compositor; + struct surface *surface = widget->surface; + + if (!surface->input_region) + surface->input_region = wl_compositor_create_region(comp); + + if (rect) { + wl_region_add(surface->input_region, + rect->x, rect->y, rect->width, rect->height); + } +} + +void +widget_set_resize_handler(struct widget *widget, + widget_resize_handler_t handler) +{ + widget->resize_handler = handler; +} + +void +widget_set_redraw_handler(struct widget *widget, + widget_redraw_handler_t handler) +{ + widget->redraw_handler = handler; +} + +void +widget_set_enter_handler(struct widget *widget, widget_enter_handler_t handler) +{ + widget->enter_handler = handler; +} + +void +widget_set_leave_handler(struct widget *widget, widget_leave_handler_t handler) +{ + widget->leave_handler = handler; +} + +void +widget_set_motion_handler(struct widget *widget, + widget_motion_handler_t handler) +{ + widget->motion_handler = handler; +} + +void +widget_set_button_handler(struct widget *widget, + widget_button_handler_t handler) +{ + widget->button_handler = handler; +} + +void +widget_set_touch_up_handler(struct widget *widget, + widget_touch_up_handler_t handler) +{ + widget->touch_up_handler = handler; +} + +void +widget_set_touch_down_handler(struct widget *widget, + widget_touch_down_handler_t handler) +{ + widget->touch_down_handler = handler; +} + +void +widget_set_touch_motion_handler(struct widget *widget, + widget_touch_motion_handler_t handler) +{ + widget->touch_motion_handler = handler; +} + +void +widget_set_touch_frame_handler(struct widget *widget, + widget_touch_frame_handler_t handler) +{ + widget->touch_frame_handler = handler; +} + +void +widget_set_touch_cancel_handler(struct widget *widget, + widget_touch_cancel_handler_t handler) +{ + widget->touch_cancel_handler = handler; +} + +void +widget_set_axis_handler(struct widget *widget, + widget_axis_handler_t handler) +{ + widget->axis_handler = handler; +} + +static void +window_schedule_redraw_task(struct window *window); + +void +widget_schedule_redraw(struct widget *widget) +{ + DBG_OBJ(widget->surface->surface, "widget %p\n", widget); + widget->surface->redraw_needed = 1; + window_schedule_redraw_task(widget->window); +} + +cairo_surface_t * +window_get_surface(struct window *window) +{ + cairo_surface_t *cairo_surface; + + cairo_surface = widget_get_cairo_surface(window->main_surface->widget); + + return cairo_surface_reference(cairo_surface); +} + +struct wl_surface * +window_get_wl_surface(struct window *window) +{ + return window->main_surface->surface; +} + +struct wl_shell_surface * +window_get_wl_shell_surface(struct window *window) +{ + return window->shell_surface; +} + +static void +tooltip_redraw_handler(struct widget *widget, void *data) +{ + cairo_t *cr; + const int32_t r = 3; + struct tooltip *tooltip = data; + int32_t width, height; + + cr = widget_cairo_create(widget); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); + cairo_paint(cr); + + width = widget->allocation.width; + height = widget->allocation.height; + rounded_rect(cr, 0, 0, width, height, r); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.4, 0.8); + cairo_fill(cr); + + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_move_to(cr, 10, 16); + cairo_show_text(cr, tooltip->entry); + cairo_destroy(cr); +} + +static cairo_text_extents_t +get_text_extents(struct tooltip *tooltip) +{ + cairo_t *cr; + cairo_text_extents_t extents; + + /* Use the dummy_surface because tooltip's surface was not + * created yet, and parent does not have a valid surface + * outside repaint, either. + */ + cr = cairo_create(tooltip->window->display->dummy_surface); + cairo_text_extents(cr, tooltip->entry, &extents); + cairo_destroy(cr); + + return extents; +} + +static int +window_create_tooltip(struct tooltip *tooltip) +{ + struct widget *parent = tooltip->parent; + struct display *display = parent->window->display; + struct window *window; + const int offset_y = 27; + const int margin = 3; + cairo_text_extents_t extents; + + if (tooltip->widget) + return 0; + + window = window_create_transient(display, parent->window, tooltip->x, + tooltip->y + offset_y, + WL_SHELL_SURFACE_TRANSIENT_INACTIVE); + if (!window) + return -1; + + tooltip->window = window; + tooltip->widget = window_add_widget(tooltip->window, tooltip); + + extents = get_text_extents(tooltip); + widget_set_redraw_handler(tooltip->widget, tooltip_redraw_handler); + window_schedule_resize(window, extents.width + 20, 20 + margin * 2); + + return 0; +} + +void +widget_destroy_tooltip(struct widget *parent) +{ + struct tooltip *tooltip = parent->tooltip; + + parent->tooltip_count = 0; + if (!tooltip) + return; + + if (tooltip->widget) { + widget_destroy(tooltip->widget); + window_destroy(tooltip->window); + tooltip->widget = NULL; + tooltip->window = NULL; + } + + close(tooltip->tooltip_fd); + free(tooltip->entry); + free(tooltip); + parent->tooltip = NULL; +} + +static void +tooltip_func(struct task *task, uint32_t events) +{ + struct tooltip *tooltip = + container_of(task, struct tooltip, tooltip_task); + uint64_t exp; + + if (read(tooltip->tooltip_fd, &exp, sizeof (uint64_t)) != sizeof (uint64_t)) + abort(); + window_create_tooltip(tooltip); +} + +#define TOOLTIP_TIMEOUT 500 +static int +tooltip_timer_reset(struct tooltip *tooltip) +{ + struct itimerspec its; + + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = TOOLTIP_TIMEOUT / 1000; + its.it_value.tv_nsec = (TOOLTIP_TIMEOUT % 1000) * 1000 * 1000; + if (timerfd_settime(tooltip->tooltip_fd, 0, &its, NULL) < 0) { + fprintf(stderr, "could not set timerfd\n: %m"); + return -1; + } + + return 0; +} + +int +widget_set_tooltip(struct widget *parent, char *entry, float x, float y) +{ + struct tooltip *tooltip = parent->tooltip; + + parent->tooltip_count++; + if (tooltip) { + tooltip->x = x; + tooltip->y = y; + tooltip_timer_reset(tooltip); + return 0; + } + + /* the handler might be triggered too fast via input device motion, so + * we need this check here to make sure tooltip is fully initialized */ + if (parent->tooltip_count > 1) + return 0; + + tooltip = malloc(sizeof *tooltip); + if (!tooltip) + return -1; + + parent->tooltip = tooltip; + tooltip->parent = parent; + tooltip->widget = NULL; + tooltip->window = NULL; + tooltip->x = x; + tooltip->y = y; + tooltip->entry = strdup(entry); + tooltip->tooltip_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + if (tooltip->tooltip_fd < 0) { + fprintf(stderr, "could not create timerfd\n: %m"); + return -1; + } + + tooltip->tooltip_task.run = tooltip_func; + display_watch_fd(parent->window->display, tooltip->tooltip_fd, + EPOLLIN, &tooltip->tooltip_task); + tooltip_timer_reset(tooltip); + + return 0; +} + +static void +workspace_manager_state(void *data, + struct workspace_manager *workspace_manager, + uint32_t current, + uint32_t count) +{ + struct display *display = data; + + display->workspace = current; + display->workspace_count = count; +} + +static const struct workspace_manager_listener workspace_manager_listener = { + workspace_manager_state +}; + +static void +frame_resize_handler(struct widget *widget, + int32_t width, int32_t height, void *data) +{ + struct frame *frame = data; + struct widget *child = frame->child; + struct rectangle allocation; + struct display *display = widget->window->display; + struct surface *surface = widget->surface; + struct frame_button * button; + struct theme *t = display->theme; + int x_l, x_r, y, w, h; + int decoration_width, decoration_height; + int opaque_margin, shadow_margin; + + switch (widget->window->type) { + case TYPE_FULLSCREEN: + decoration_width = 0; + decoration_height = 0; + + allocation.x = 0; + allocation.y = 0; + allocation.width = width; + allocation.height = height; + opaque_margin = 0; + + wl_list_for_each(button, &frame->buttons_list, link) + button->widget->opaque = 1; + break; + case TYPE_MAXIMIZED: + decoration_width = t->width * 2; + decoration_height = t->width + t->titlebar_height; + + allocation.x = t->width; + allocation.y = t->titlebar_height; + allocation.width = width - decoration_width; + allocation.height = height - decoration_height; + + opaque_margin = 0; + + wl_list_for_each(button, &frame->buttons_list, link) + button->widget->opaque = 0; + break; + default: + decoration_width = (t->width + t->margin) * 2; + decoration_height = t->width + + t->titlebar_height + t->margin * 2; + + allocation.x = t->width + t->margin; + allocation.y = t->titlebar_height + t->margin; + allocation.width = width - decoration_width; + allocation.height = height - decoration_height; + + opaque_margin = t->margin + t->frame_radius; + + wl_list_for_each(button, &frame->buttons_list, link) + button->widget->opaque = 0; + break; + } + + widget_set_allocation(child, allocation.x, allocation.y, + allocation.width, allocation.height); + + if (child->resize_handler) + child->resize_handler(child, + allocation.width, + allocation.height, + child->user_data); + + width = child->allocation.width + decoration_width; + height = child->allocation.height + decoration_height; + + shadow_margin = widget->window->type == TYPE_MAXIMIZED ? 0 : t->margin; + + surface->input_region = + wl_compositor_create_region(display->compositor); + if (widget->window->type != TYPE_FULLSCREEN) { + wl_region_add(surface->input_region, + shadow_margin, shadow_margin, + width - 2 * shadow_margin, + height - 2 * shadow_margin); + } else { + wl_region_add(surface->input_region, 0, 0, width, height); + } + + widget_set_allocation(widget, 0, 0, width, height); + + if (child->opaque) + wl_region_add(surface->opaque_region, + opaque_margin, opaque_margin, + widget->allocation.width - 2 * opaque_margin, + widget->allocation.height - 2 * opaque_margin); + + /* frame internal buttons */ + x_r = frame->widget->allocation.width - t->width - shadow_margin; + x_l = t->width + shadow_margin; + y = t->width + shadow_margin; + wl_list_for_each(button, &frame->buttons_list, link) { + const int button_padding = 4; + w = cairo_image_surface_get_width(button->icon); + h = cairo_image_surface_get_height(button->icon); + + if (button->decoration == FRAME_BUTTON_FANCY) + w += 10; + + if (button->align == FRAME_BUTTON_LEFT) { + widget_set_allocation(button->widget, + x_l, y , w + 1, h + 1); + x_l += w; + x_l += button_padding; + } else { + x_r -= w; + widget_set_allocation(button->widget, + x_r, y , w + 1, h + 1); + x_r -= button_padding; + } + } +} + +static int +frame_button_enter_handler(struct widget *widget, + struct input *input, float x, float y, void *data) +{ + struct frame_button *frame_button = data; + + widget_schedule_redraw(frame_button->widget); + frame_button->state = FRAME_BUTTON_OVER; + + return CURSOR_LEFT_PTR; +} + +static void +frame_button_leave_handler(struct widget *widget, struct input *input, void *data) +{ + struct frame_button *frame_button = data; + + widget_schedule_redraw(frame_button->widget); + frame_button->state = FRAME_BUTTON_DEFAULT; +} + +static void +frame_button_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, void *data) +{ + struct frame_button *frame_button = data; + struct window *window = widget->window; + int was_pressed = (frame_button->state == FRAME_BUTTON_ACTIVE); + + if (button != BTN_LEFT) + return; + + switch (state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + frame_button->state = FRAME_BUTTON_ACTIVE; + widget_schedule_redraw(frame_button->widget); + + if (frame_button->type == FRAME_BUTTON_ICON) + window_show_frame_menu(window, input, time); + return; + case WL_POINTER_BUTTON_STATE_RELEASED: + frame_button->state = FRAME_BUTTON_DEFAULT; + widget_schedule_redraw(frame_button->widget); + break; + } + + if (!was_pressed) + return; + + switch (frame_button->type) { + case FRAME_BUTTON_CLOSE: + if (window->close_handler) + window->close_handler(window->parent, + window->user_data); + else + display_exit(window->display); + break; + case FRAME_BUTTON_MINIMIZE: + fprintf(stderr,"Minimize stub\n"); + break; + case FRAME_BUTTON_MAXIMIZE: + window_set_maximized(window, window->type != TYPE_MAXIMIZED); + break; + default: + /* Unknown operation */ + break; + } +} + +static void +frame_button_touch_down_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float x, float y, void *data) +{ + struct frame_button *frame_button = data; + struct window *window = widget->window; + + switch (frame_button->type) { + case FRAME_BUTTON_CLOSE: + if (window->close_handler) + window->close_handler(window->parent, + window->user_data); + else + display_exit(window->display); + break; + case FRAME_BUTTON_MINIMIZE: + fprintf(stderr,"Minimize stub\n"); + break; + case FRAME_BUTTON_MAXIMIZE: + window_set_maximized(window, window->type != TYPE_MAXIMIZED); + break; + default: + /* Unknown operation */ + break; + } +} + + +static int +frame_button_motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct frame_button *frame_button = data; + enum frame_button_pointer previous_button_state = frame_button->state; + + /* only track state for a pressed button */ + if (input->grab != widget) + return CURSOR_LEFT_PTR; + + if (x > widget->allocation.x && + x < (widget->allocation.x + widget->allocation.width) && + y > widget->allocation.y && + y < (widget->allocation.y + widget->allocation.height)) { + frame_button->state = FRAME_BUTTON_ACTIVE; + } else { + frame_button->state = FRAME_BUTTON_DEFAULT; + } + + if (frame_button->state != previous_button_state) + widget_schedule_redraw(frame_button->widget); + + return CURSOR_LEFT_PTR; +} + +static void +frame_button_redraw_handler(struct widget *widget, void *data) +{ + struct frame_button *frame_button = data; + cairo_t *cr; + int width, height, x, y; + + x = widget->allocation.x; + y = widget->allocation.y; + width = widget->allocation.width; + height = widget->allocation.height; + + if (!width) + return; + if (!height) + return; + if (widget->opaque) + return; + + cr = widget_cairo_create(widget); + + if (frame_button->decoration == FRAME_BUTTON_FANCY) { + cairo_set_line_width(cr, 1); + + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_rectangle (cr, x, y, 25, 16); + + cairo_stroke_preserve(cr); + + switch (frame_button->state) { + case FRAME_BUTTON_DEFAULT: + cairo_set_source_rgb(cr, 0.88, 0.88, 0.88); + break; + case FRAME_BUTTON_OVER: + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + break; + case FRAME_BUTTON_ACTIVE: + cairo_set_source_rgb(cr, 0.7, 0.7, 0.7); + break; + } + + cairo_fill (cr); + + x += 4; + } + + cairo_set_source_surface(cr, frame_button->icon, x, y); + cairo_paint(cr); + + cairo_destroy(cr); +} + +static struct widget * +frame_button_create(struct frame *frame, void *data, enum frame_button_action type, + enum frame_button_align align, enum frame_button_decoration style) +{ + struct frame_button *frame_button; + const char *icon = data; + + frame_button = xzalloc (sizeof *frame_button); + frame_button->icon = cairo_image_surface_create_from_png(icon); + frame_button->widget = widget_add_widget(frame->widget, frame_button); + frame_button->frame = frame; + frame_button->type = type; + frame_button->align = align; + frame_button->decoration = style; + + wl_list_insert(frame->buttons_list.prev, &frame_button->link); + + widget_set_redraw_handler(frame_button->widget, frame_button_redraw_handler); + widget_set_enter_handler(frame_button->widget, frame_button_enter_handler); + widget_set_leave_handler(frame_button->widget, frame_button_leave_handler); + widget_set_touch_down_handler(frame_button->widget, frame_button_touch_down_handler); + widget_set_button_handler(frame_button->widget, frame_button_button_handler); + widget_set_motion_handler(frame_button->widget, frame_button_motion_handler); + return frame_button->widget; +} + +static void +frame_button_destroy(struct frame_button *frame_button) +{ + widget_destroy(frame_button->widget); + wl_list_remove(&frame_button->link); + cairo_surface_destroy(frame_button->icon); + free(frame_button); + + return; +} + +static void +frame_redraw_handler(struct widget *widget, void *data) +{ + cairo_t *cr; + struct window *window = widget->window; + struct theme *t = window->display->theme; + uint32_t flags = 0; + + if (window->type == TYPE_FULLSCREEN) + return; + + cr = widget_cairo_create(widget); + + if (window->focus_count) + flags |= THEME_FRAME_ACTIVE; + if (window->type == TYPE_MAXIMIZED) + flags |= THEME_FRAME_MAXIMIZED; + theme_render_frame(t, cr, widget->allocation.width, + widget->allocation.height, window->title, flags); + + cairo_destroy(cr); +} + +static int +frame_get_pointer_image_for_location(struct frame *frame, struct input *input) +{ + struct theme *t = frame->widget->window->display->theme; + struct window *window = frame->widget->window; + int location; + + if (window->type != TYPE_TOPLEVEL) + return CURSOR_LEFT_PTR; + + location = theme_get_location(t, input->sx, input->sy, + frame->widget->allocation.width, + frame->widget->allocation.height, + window->type == TYPE_MAXIMIZED ? + THEME_FRAME_MAXIMIZED : 0); + + switch (location) { + case THEME_LOCATION_RESIZING_TOP: + return CURSOR_TOP; + case THEME_LOCATION_RESIZING_BOTTOM: + return CURSOR_BOTTOM; + case THEME_LOCATION_RESIZING_LEFT: + return CURSOR_LEFT; + case THEME_LOCATION_RESIZING_RIGHT: + return CURSOR_RIGHT; + case THEME_LOCATION_RESIZING_TOP_LEFT: + return CURSOR_TOP_LEFT; + case THEME_LOCATION_RESIZING_TOP_RIGHT: + return CURSOR_TOP_RIGHT; + case THEME_LOCATION_RESIZING_BOTTOM_LEFT: + return CURSOR_BOTTOM_LEFT; + case THEME_LOCATION_RESIZING_BOTTOM_RIGHT: + return CURSOR_BOTTOM_RIGHT; + case THEME_LOCATION_EXTERIOR: + case THEME_LOCATION_TITLEBAR: + default: + return CURSOR_LEFT_PTR; + } +} + +static void +frame_menu_func(struct window *window, int index, void *data) +{ + struct display *display; + + switch (index) { + case 0: /* close */ + if (window->close_handler) + window->close_handler(window->parent, + window->user_data); + else + display_exit(window->display); + break; + case 1: /* move to workspace above */ + display = window->display; + if (display->workspace > 0) + workspace_manager_move_surface( + display->workspace_manager, + window->main_surface->surface, + display->workspace - 1); + break; + case 2: /* move to workspace below */ + display = window->display; + if (display->workspace < display->workspace_count - 1) + workspace_manager_move_surface( + display->workspace_manager, + window->main_surface->surface, + display->workspace + 1); + break; + case 3: /* fullscreen */ + /* we don't have a way to get out of fullscreen for now */ + if (window->fullscreen_handler) + window->fullscreen_handler(window, window->user_data); + break; + } +} + +void +window_show_frame_menu(struct window *window, + struct input *input, uint32_t time) +{ + int32_t x, y; + int count; + + static const char *entries[] = { + "Close", + "Move to workspace above", "Move to workspace below", + "Fullscreen" + }; + + if (window->fullscreen_handler) + count = ARRAY_LENGTH(entries); + else + count = ARRAY_LENGTH(entries) - 1; + + input_get_position(input, &x, &y); + window_show_menu(window->display, input, time, window, + x - 10, y - 10, frame_menu_func, entries, count); +} + +static int +frame_enter_handler(struct widget *widget, + struct input *input, float x, float y, void *data) +{ + return frame_get_pointer_image_for_location(data, input); +} + +static int +frame_motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + return frame_get_pointer_image_for_location(data, input); +} + +static void +frame_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, enum wl_pointer_button_state state, + void *data) + +{ + struct frame *frame = data; + struct window *window = widget->window; + struct display *display = window->display; + int location; + + if (state != WL_POINTER_BUTTON_STATE_PRESSED) + return; + + location = theme_get_location(display->theme, input->sx, input->sy, + frame->widget->allocation.width, + frame->widget->allocation.height, + window->type == TYPE_MAXIMIZED ? + THEME_FRAME_MAXIMIZED : 0); + + if (window->display->shell && button == BTN_LEFT && + window->type == TYPE_TOPLEVEL) { + switch (location) { + case THEME_LOCATION_TITLEBAR: + if (!window->shell_surface) + break; + input_ungrab(input); + wl_shell_surface_move(window->shell_surface, + input_get_seat(input), + display->serial); + break; + case THEME_LOCATION_RESIZING_TOP: + case THEME_LOCATION_RESIZING_BOTTOM: + case THEME_LOCATION_RESIZING_LEFT: + case THEME_LOCATION_RESIZING_RIGHT: + case THEME_LOCATION_RESIZING_TOP_LEFT: + case THEME_LOCATION_RESIZING_TOP_RIGHT: + case THEME_LOCATION_RESIZING_BOTTOM_LEFT: + case THEME_LOCATION_RESIZING_BOTTOM_RIGHT: + if (!window->shell_surface) + break; + input_ungrab(input); + + window->resizing = 1; + wl_shell_surface_resize(window->shell_surface, + input_get_seat(input), + display->serial, location); + break; + } + } else if (button == BTN_RIGHT && + (window->type == TYPE_TOPLEVEL || + window->type == TYPE_MAXIMIZED)) { + window_show_frame_menu(window, input, time); + } +} + +static void +frame_touch_down_handler(struct widget *widget, struct input *input, + uint32_t serial, uint32_t time, int32_t id, + float x, float y, void *data) +{ + struct window *window = widget->window; + struct display *display = window->display; + + wl_shell_surface_move(window->shell_surface, + input_get_seat(input), + display->serial); +} + +struct widget * +frame_create(struct window *window, void *data) +{ + struct frame *frame; + + frame = xzalloc(sizeof *frame); + frame->widget = window_add_widget(window, frame); + frame->child = widget_add_widget(frame->widget, data); + + widget_set_redraw_handler(frame->widget, frame_redraw_handler); + widget_set_resize_handler(frame->widget, frame_resize_handler); + widget_set_enter_handler(frame->widget, frame_enter_handler); + widget_set_motion_handler(frame->widget, frame_motion_handler); + widget_set_button_handler(frame->widget, frame_button_handler); + widget_set_touch_down_handler(frame->widget, frame_touch_down_handler); + + /* Create empty list for frame buttons */ + wl_list_init(&frame->buttons_list); + + frame_button_create(frame, DATADIR "/weston/icon_window.png", + FRAME_BUTTON_ICON, FRAME_BUTTON_LEFT, FRAME_BUTTON_NONE); + + frame_button_create(frame, DATADIR "/weston/sign_close.png", + FRAME_BUTTON_CLOSE, FRAME_BUTTON_RIGHT, FRAME_BUTTON_FANCY); + + frame_button_create(frame, DATADIR "/weston/sign_maximize.png", + FRAME_BUTTON_MAXIMIZE, FRAME_BUTTON_RIGHT, FRAME_BUTTON_FANCY); + + frame_button_create(frame, DATADIR "/weston/sign_minimize.png", + FRAME_BUTTON_MINIMIZE, FRAME_BUTTON_RIGHT, FRAME_BUTTON_FANCY); + + window->frame = frame; + + return frame->child; +} + +void +frame_set_child_size(struct widget *widget, int child_width, int child_height) +{ + struct display *display = widget->window->display; + struct theme *t = display->theme; + int decoration_width, decoration_height; + int width, height; + int margin = widget->window->type == TYPE_MAXIMIZED ? 0 : t->margin; + + if (widget->window->type != TYPE_FULLSCREEN) { + decoration_width = (t->width + margin) * 2; + decoration_height = t->width + + t->titlebar_height + margin * 2; + + width = child_width + decoration_width; + height = child_height + decoration_height; + } else { + width = child_width; + height = child_height; + } + + window_schedule_resize(widget->window, width, height); +} + +static void +frame_destroy(struct frame *frame) +{ + struct frame_button *button, *tmp; + + wl_list_for_each_safe(button, tmp, &frame->buttons_list, link) + frame_button_destroy(button); + + /* frame->child must be destroyed by the application */ + widget_destroy(frame->widget); + free(frame); +} + +static void +input_set_focus_widget(struct input *input, struct widget *focus, + float x, float y) +{ + struct widget *old, *widget; + int cursor; + + if (focus == input->focus_widget) + return; + + old = input->focus_widget; + if (old) { + widget = old; + if (input->grab) + widget = input->grab; + if (widget->leave_handler) + widget->leave_handler(old, input, widget->user_data); + input->focus_widget = NULL; + } + + if (focus) { + widget = focus; + if (input->grab) + widget = input->grab; + input->focus_widget = focus; + if (widget->enter_handler) + cursor = widget->enter_handler(focus, input, x, y, + widget->user_data); + else + cursor = widget->default_cursor; + + input_set_pointer_image(input, cursor); + } +} + +void +input_grab(struct input *input, struct widget *widget, uint32_t button) +{ + input->grab = widget; + input->grab_button = button; +} + +void +input_ungrab(struct input *input) +{ + struct widget *widget; + + input->grab = NULL; + if (input->pointer_focus) { + widget = window_find_widget(input->pointer_focus, + input->sx, input->sy); + input_set_focus_widget(input, widget, input->sx, input->sy); + } +} + +static void +input_remove_pointer_focus(struct input *input) +{ + struct window *window = input->pointer_focus; + + if (!window) + return; + + input_set_focus_widget(input, NULL, 0, 0); + + input->pointer_focus = NULL; + input->current_cursor = CURSOR_UNSET; +} + +static void +pointer_handle_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t sx_w, wl_fixed_t sy_w) +{ + struct input *input = data; + struct window *window; + struct widget *widget; + float sx = wl_fixed_to_double(sx_w); + float sy = wl_fixed_to_double(sy_w); + + if (!surface) { + /* enter event for a window we've just destroyed */ + return; + } + + input->display->serial = serial; + input->pointer_enter_serial = serial; + input->pointer_focus = wl_surface_get_user_data(surface); + window = input->pointer_focus; + + if (window->resizing) { + window->resizing = 0; + /* Schedule a redraw to free the pool */ + window_schedule_redraw(window); + } + + input->sx = sx; + input->sy = sy; + + widget = window_find_widget(window, sx, sy); + input_set_focus_widget(input, widget, sx, sy); +} + +static void +pointer_handle_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) +{ + struct input *input = data; + + input->display->serial = serial; + input_remove_pointer_focus(input); +} + +static void +pointer_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w) +{ + struct input *input = data; + struct window *window = input->pointer_focus; + struct widget *widget; + int cursor; + float sx = wl_fixed_to_double(sx_w); + float sy = wl_fixed_to_double(sy_w); + + input->sx = sx; + input->sy = sy; + + if (!window) + return; + + /* when making the window smaller - e.g. after a unmaximise we might + * still have a pending motion event that the compositor has picked + * based on the old surface dimensions + */ + if (sx > window->main_surface->allocation.width || + sy > window->main_surface->allocation.height) + return; + + if (!(input->grab && input->grab_button)) { + widget = window_find_widget(window, sx, sy); + input_set_focus_widget(input, widget, sx, sy); + } + + if (input->grab) + widget = input->grab; + else + widget = input->focus_widget; + if (widget) { + if (widget->motion_handler) + cursor = widget->motion_handler(input->focus_widget, + input, time, sx, sy, + widget->user_data); + else + cursor = widget->default_cursor; + } else + cursor = CURSOR_LEFT_PTR; + + input_set_pointer_image(input, cursor); +} + +static void +pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state_w) +{ + struct input *input = data; + struct widget *widget; + enum wl_pointer_button_state state = state_w; + + input->display->serial = serial; + if (input->focus_widget && input->grab == NULL && + state == WL_POINTER_BUTTON_STATE_PRESSED) + input_grab(input, input->focus_widget, button); + + widget = input->grab; + if (widget && widget->button_handler) + (*widget->button_handler)(widget, + input, time, + button, state, + input->grab->user_data); + + if (input->grab && input->grab_button == button && + state == WL_POINTER_BUTTON_STATE_RELEASED) + input_ungrab(input); +} + +static void +pointer_handle_axis(void *data, struct wl_pointer *pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ + struct input *input = data; + struct widget *widget; + + widget = input->focus_widget; + if (input->grab) + widget = input->grab; + if (widget && widget->axis_handler) + (*widget->axis_handler)(widget, + input, time, + axis, value, + widget->user_data); +} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, +}; + +static void +input_remove_keyboard_focus(struct input *input) +{ + struct window *window = input->keyboard_focus; + struct itimerspec its; + + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = 0; + its.it_value.tv_nsec = 0; + timerfd_settime(input->repeat_timer_fd, 0, &its, NULL); + + if (!window) + return; + + window->focus_count--; + if (window->keyboard_focus_handler) + (*window->keyboard_focus_handler)(window, NULL, + window->user_data); + + input->keyboard_focus = NULL; +} + +static void +keyboard_repeat_func(struct task *task, uint32_t events) +{ + struct input *input = + container_of(task, struct input, repeat_task); + struct window *window = input->keyboard_focus; + uint64_t exp; + + if (read(input->repeat_timer_fd, &exp, sizeof exp) != sizeof exp) + /* If we change the timer between the fd becoming + * readable and getting here, there'll be nothing to + * read and we get EAGAIN. */ + return; + + if (window && window->key_handler) { + (*window->key_handler)(window, input, input->repeat_time, + input->repeat_key, input->repeat_sym, + WL_KEYBOARD_KEY_STATE_PRESSED, + window->user_data); + } +} + +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, + uint32_t format, int fd, uint32_t size) +{ + struct input *input = data; + char *map_str; + + if (!data) { + close(fd); + return; + } + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + + map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (map_str == MAP_FAILED) { + close(fd); + return; + } + + input->xkb.keymap = xkb_map_new_from_string(input->display->xkb_context, + map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + 0); + munmap(map_str, size); + close(fd); + + if (!input->xkb.keymap) { + fprintf(stderr, "failed to compile keymap\n"); + return; + } + + input->xkb.state = xkb_state_new(input->xkb.keymap); + if (!input->xkb.state) { + fprintf(stderr, "failed to create XKB state\n"); + xkb_map_unref(input->xkb.keymap); + input->xkb.keymap = NULL; + return; + } + + input->xkb.control_mask = + 1 << xkb_map_mod_get_index(input->xkb.keymap, "Control"); + input->xkb.alt_mask = + 1 << xkb_map_mod_get_index(input->xkb.keymap, "Mod1"); + input->xkb.shift_mask = + 1 << xkb_map_mod_get_index(input->xkb.keymap, "Shift"); +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ + struct input *input = data; + struct window *window; + + input->display->serial = serial; + input->keyboard_focus = wl_surface_get_user_data(surface); + + window = input->keyboard_focus; + window->focus_count++; + if (window->keyboard_focus_handler) + (*window->keyboard_focus_handler)(window, + input, window->user_data); +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, + uint32_t serial, struct wl_surface *surface) +{ + struct input *input = data; + + input->display->serial = serial; + input_remove_keyboard_focus(input); +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state_w) +{ + struct input *input = data; + struct window *window = input->keyboard_focus; + uint32_t code, num_syms; + enum wl_keyboard_key_state state = state_w; + const xkb_keysym_t *syms; + xkb_keysym_t sym; + struct itimerspec its; + + input->display->serial = serial; + code = key + 8; + if (!window || !input->xkb.state) + return; + + num_syms = xkb_key_get_syms(input->xkb.state, code, &syms); + + sym = XKB_KEY_NoSymbol; + if (num_syms == 1) + sym = syms[0]; + + if (sym == XKB_KEY_F5 && input->modifiers == MOD_ALT_MASK) { + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + window_set_maximized(window, + window->type != TYPE_MAXIMIZED); + } else if (sym == XKB_KEY_F11 && + window->fullscreen_handler && + state == WL_KEYBOARD_KEY_STATE_PRESSED) { + window->fullscreen_handler(window, window->user_data); + } else if (sym == XKB_KEY_F4 && + input->modifiers == MOD_ALT_MASK && + state == WL_KEYBOARD_KEY_STATE_PRESSED) { + if (window->close_handler) + window->close_handler(window->parent, + window->user_data); + else + display_exit(window->display); + } else if (window->key_handler) { + (*window->key_handler)(window, input, time, key, + sym, state, window->user_data); + } + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED && + key == input->repeat_key) { + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = 0; + its.it_value.tv_nsec = 0; + timerfd_settime(input->repeat_timer_fd, 0, &its, NULL); + } else if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + input->repeat_sym = sym; + input->repeat_key = key; + input->repeat_time = time; + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 25 * 1000 * 1000; + its.it_value.tv_sec = 0; + its.it_value.tv_nsec = 400 * 1000 * 1000; + timerfd_settime(input->repeat_timer_fd, 0, &its, NULL); + } +} + +static void +keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ + struct input *input = data; + xkb_mod_mask_t mask; + + /* If we're not using a keymap, then we don't handle PC-style modifiers */ + if (!input->xkb.keymap) + return; + + xkb_state_update_mask(input->xkb.state, mods_depressed, mods_latched, + mods_locked, 0, 0, group); + mask = xkb_state_serialize_mods(input->xkb.state, + XKB_STATE_DEPRESSED | + XKB_STATE_LATCHED); + input->modifiers = 0; + if (mask & input->xkb.control_mask) + input->modifiers |= MOD_CONTROL_MASK; + if (mask & input->xkb.alt_mask) + input->modifiers |= MOD_ALT_MASK; + if (mask & input->xkb.shift_mask) + input->modifiers |= MOD_SHIFT_MASK; +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, +}; + +static void +touch_handle_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, struct wl_surface *surface, + int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct input *input = data; + struct widget *widget; + float sx = wl_fixed_to_double(x_w); + float sy = wl_fixed_to_double(y_w); + + input->display->serial = serial; + input->touch_focus = wl_surface_get_user_data(surface); + if (!input->touch_focus) { + DBG("Failed to find to touch focus for surface %p\n", surface); + return; + } + + widget = window_find_widget(input->touch_focus, + wl_fixed_to_double(x_w), + wl_fixed_to_double(y_w)); + if (widget) { + struct touch_point *tp = xmalloc(sizeof *tp); + if (tp) { + tp->id = id; + tp->widget = widget; + wl_list_insert(&input->touch_point_list, &tp->link); + + if (widget->touch_down_handler) + (*widget->touch_down_handler)(widget, input, + serial, time, id, + sx, sy, + widget->user_data); + } + } +} + +static void +touch_handle_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, int32_t id) +{ + struct input *input = data; + struct touch_point *tp, *tmp; + + if (!input->touch_focus) { + DBG("No touch focus found for touch up event!\n"); + return; + } + + wl_list_for_each_safe(tp, tmp, &input->touch_point_list, link) { + if (tp->id != id) + continue; + + if (tp->widget->touch_up_handler) + (*tp->widget->touch_up_handler)(tp->widget, input, serial, + time, id, + tp->widget->user_data); + + wl_list_remove(&tp->link); + free(tp); + + return; + } +} + +static void +touch_handle_motion(void *data, struct wl_touch *wl_touch, + uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct input *input = data; + struct touch_point *tp; + float sx = wl_fixed_to_double(x_w); + float sy = wl_fixed_to_double(y_w); + + DBG("touch_handle_motion: %i %i\n", id, wl_list_length(&input->touch_point_list)); + + if (!input->touch_focus) { + DBG("No touch focus found for touch motion event!\n"); + return; + } + + wl_list_for_each(tp, &input->touch_point_list, link) { + if (tp->id != id) + continue; + + if (tp->widget->touch_motion_handler) + (*tp->widget->touch_motion_handler)(tp->widget, input, time, + id, sx, sy, + tp->widget->user_data); + return; + } +} + +static void +touch_handle_frame(void *data, struct wl_touch *wl_touch) +{ + struct input *input = data; + struct touch_point *tp, *tmp; + + DBG("touch_handle_frame\n"); + + if (!input->touch_focus) { + DBG("No touch focus found for touch frame event!\n"); + return; + } + + wl_list_for_each_safe(tp, tmp, &input->touch_point_list, link) { + if (tp->widget->touch_frame_handler) + (*tp->widget->touch_frame_handler)(tp->widget, input, + tp->widget->user_data); + + wl_list_remove(&tp->link); + free(tp); + } +} + +static void +touch_handle_cancel(void *data, struct wl_touch *wl_touch) +{ + struct input *input = data; + struct touch_point *tp, *tmp; + + DBG("touch_handle_cancel\n"); + + if (!input->touch_focus) { + DBG("No touch focus found for touch cancel event!\n"); + return; + } + + wl_list_for_each_safe(tp, tmp, &input->touch_point_list, link) { + if (tp->widget->touch_cancel_handler) + (*tp->widget->touch_cancel_handler)(tp->widget, input, + tp->widget->user_data); + + wl_list_remove(&tp->link); + free(tp); + } +} + +static const struct wl_touch_listener touch_listener = { + touch_handle_down, + touch_handle_up, + touch_handle_motion, + touch_handle_frame, + touch_handle_cancel, +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + struct input *input = data; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !input->pointer) { + input->pointer = wl_seat_get_pointer(seat); + wl_pointer_set_user_data(input->pointer, input); + wl_pointer_add_listener(input->pointer, &pointer_listener, + input); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->pointer) { + wl_pointer_destroy(input->pointer); + input->pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard) { + input->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_set_user_data(input->keyboard, input); + wl_keyboard_add_listener(input->keyboard, &keyboard_listener, + input); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard) { + wl_keyboard_destroy(input->keyboard); + input->keyboard = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch) { + input->touch = wl_seat_get_touch(seat); + wl_touch_set_user_data(input->touch, input); + wl_touch_add_listener(input->touch, &touch_listener, input); + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->touch) { + wl_touch_destroy(input->touch); + input->touch = NULL; + } +} + +static void +seat_handle_name(void *data, struct wl_seat *seat, + const char *name) +{ + +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, + seat_handle_name +}; + +void +input_get_position(struct input *input, int32_t *x, int32_t *y) +{ + *x = input->sx; + *y = input->sy; +} + +struct display * +input_get_display(struct input *input) +{ + return input->display; +} + +struct wl_seat * +input_get_seat(struct input *input) +{ + return input->seat; +} + +uint32_t +input_get_modifiers(struct input *input) +{ + return input->modifiers; +} + +struct widget * +input_get_focus_widget(struct input *input) +{ + return input->focus_widget; +} + +struct data_offer { + struct wl_data_offer *offer; + struct input *input; + struct wl_array types; + int refcount; + + struct task io_task; + int fd; + data_func_t func; + int32_t x, y; + void *user_data; +}; + +static void +data_offer_offer(void *data, struct wl_data_offer *wl_data_offer, const char *type) +{ + struct data_offer *offer = data; + char **p; + + p = wl_array_add(&offer->types, sizeof *p); + *p = strdup(type); +} + +static const struct wl_data_offer_listener data_offer_listener = { + data_offer_offer, +}; + +static void +data_offer_destroy(struct data_offer *offer) +{ + char **p; + + offer->refcount--; + if (offer->refcount == 0) { + wl_data_offer_destroy(offer->offer); + for (p = offer->types.data; *p; p++) + free(*p); + wl_array_release(&offer->types); + free(offer); + } +} + +static void +data_device_data_offer(void *data, + struct wl_data_device *data_device, + struct wl_data_offer *_offer) +{ + struct data_offer *offer; + + offer = xmalloc(sizeof *offer); + + wl_array_init(&offer->types); + offer->refcount = 1; + offer->input = data; + offer->offer = _offer; + wl_data_offer_add_listener(offer->offer, + &data_offer_listener, offer); +} + +static void +data_device_enter(void *data, struct wl_data_device *data_device, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t x_w, wl_fixed_t y_w, + struct wl_data_offer *offer) +{ + struct input *input = data; + struct window *window; + void *types_data; + float x = wl_fixed_to_double(x_w); + float y = wl_fixed_to_double(y_w); + char **p; + + input->pointer_enter_serial = serial; + window = wl_surface_get_user_data(surface); + input->pointer_focus = window; + + if (offer) { + input->drag_offer = wl_data_offer_get_user_data(offer); + + p = wl_array_add(&input->drag_offer->types, sizeof *p); + *p = NULL; + + types_data = input->drag_offer->types.data; + } else { + input->drag_offer = NULL; + types_data = NULL; + } + + window = input->pointer_focus; + if (window->data_handler) + window->data_handler(window, input, x, y, types_data, + window->user_data); +} + +static void +data_device_leave(void *data, struct wl_data_device *data_device) +{ + struct input *input = data; + + if (input->drag_offer) { + data_offer_destroy(input->drag_offer); + input->drag_offer = NULL; + } +} + +static void +data_device_motion(void *data, struct wl_data_device *data_device, + uint32_t time, wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct input *input = data; + struct window *window = input->pointer_focus; + float x = wl_fixed_to_double(x_w); + float y = wl_fixed_to_double(y_w); + void *types_data; + + input->sx = x; + input->sy = y; + + if (input->drag_offer) + types_data = input->drag_offer->types.data; + else + types_data = NULL; + + if (window->data_handler) + window->data_handler(window, input, x, y, types_data, + window->user_data); +} + +static void +data_device_drop(void *data, struct wl_data_device *data_device) +{ + struct input *input = data; + struct window *window = input->pointer_focus; + + if (window->drop_handler) + window->drop_handler(window, input, + input->sx, input->sy, window->user_data); +} + +static void +data_device_selection(void *data, + struct wl_data_device *wl_data_device, + struct wl_data_offer *offer) +{ + struct input *input = data; + char **p; + + if (input->selection_offer) + data_offer_destroy(input->selection_offer); + + if (offer) { + input->selection_offer = wl_data_offer_get_user_data(offer); + p = wl_array_add(&input->selection_offer->types, sizeof *p); + *p = NULL; + } else { + input->selection_offer = NULL; + } +} + +static const struct wl_data_device_listener data_device_listener = { + data_device_data_offer, + data_device_enter, + data_device_leave, + data_device_motion, + data_device_drop, + data_device_selection +}; + +static void +input_set_pointer_image_index(struct input *input, int index) +{ + struct wl_buffer *buffer; + struct wl_cursor *cursor; + struct wl_cursor_image *image; + + if (!input->pointer) + return; + + cursor = input->display->cursors[input->current_cursor]; + if (!cursor) + return; + + if (index >= (int) cursor->image_count) { + fprintf(stderr, "cursor index out of range\n"); + return; + } + + image = cursor->images[index]; + buffer = wl_cursor_image_get_buffer(image); + if (!buffer) + return; + + wl_pointer_set_cursor(input->pointer, input->pointer_enter_serial, + input->pointer_surface, + image->hotspot_x, image->hotspot_y); + wl_surface_attach(input->pointer_surface, buffer, 0, 0); + wl_surface_damage(input->pointer_surface, 0, 0, + image->width, image->height); + wl_surface_commit(input->pointer_surface); +} + +static const struct wl_callback_listener pointer_surface_listener; + +static void +pointer_surface_frame_callback(void *data, struct wl_callback *callback, + uint32_t time) +{ + struct input *input = data; + struct wl_cursor *cursor; + int i; + + if (callback) { + assert(callback == input->cursor_frame_cb); + wl_callback_destroy(callback); + input->cursor_frame_cb = NULL; + } + + if (!input->pointer) + return; + + if (input->current_cursor == CURSOR_BLANK) { + wl_pointer_set_cursor(input->pointer, + input->pointer_enter_serial, + NULL, 0, 0); + return; + } + + if (input->current_cursor == CURSOR_UNSET) + return; + cursor = input->display->cursors[input->current_cursor]; + if (!cursor) + return; + + /* FIXME We don't have the current time on the first call so we set + * the animation start to the time of the first frame callback. */ + if (time == 0) + input->cursor_anim_start = 0; + else if (input->cursor_anim_start == 0) + input->cursor_anim_start = time; + + if (time == 0 || input->cursor_anim_start == 0) + i = 0; + else + i = wl_cursor_frame(cursor, time - input->cursor_anim_start); + + if (cursor->image_count > 1) { + input->cursor_frame_cb = + wl_surface_frame(input->pointer_surface); + wl_callback_add_listener(input->cursor_frame_cb, + &pointer_surface_listener, input); + } + + input_set_pointer_image_index(input, i); +} + +static const struct wl_callback_listener pointer_surface_listener = { + pointer_surface_frame_callback +}; + +void +input_set_pointer_image(struct input *input, int pointer) +{ + int force = 0; + + if (!input->pointer) + return; + + if (input->pointer_enter_serial > input->cursor_serial) + force = 1; + + if (!force && pointer == input->current_cursor) + return; + + input->current_cursor = pointer; + input->cursor_serial = input->pointer_enter_serial; + if (!input->cursor_frame_cb) + pointer_surface_frame_callback(input, NULL, 0); + else if (force) { + /* The current frame callback may be stuck if, for instance, + * the set cursor request was processed by the server after + * this client lost the focus. In this case the cursor surface + * might not be mapped and the frame callback wouldn't ever + * complete. Send a set_cursor and attach to try to map the + * cursor surface again so that the callback will finish */ + input_set_pointer_image_index(input, 0); + } +} + +struct wl_data_device * +input_get_data_device(struct input *input) +{ + return input->data_device; +} + +void +input_set_selection(struct input *input, + struct wl_data_source *source, uint32_t time) +{ + wl_data_device_set_selection(input->data_device, source, time); +} + +void +input_accept(struct input *input, const char *type) +{ + wl_data_offer_accept(input->drag_offer->offer, + input->pointer_enter_serial, type); +} + +static void +offer_io_func(struct task *task, uint32_t events) +{ + struct data_offer *offer = + container_of(task, struct data_offer, io_task); + unsigned int len; + char buffer[4096]; + + len = read(offer->fd, buffer, sizeof buffer); + offer->func(buffer, len, + offer->x, offer->y, offer->user_data); + + if (len == 0) { + close(offer->fd); + data_offer_destroy(offer); + } +} + +static void +data_offer_receive_data(struct data_offer *offer, const char *mime_type, + data_func_t func, void *user_data) +{ + int p[2]; + + if (pipe2(p, O_CLOEXEC) == -1) + return; + + wl_data_offer_receive(offer->offer, mime_type, p[1]); + close(p[1]); + + offer->io_task.run = offer_io_func; + offer->fd = p[0]; + offer->func = func; + offer->refcount++; + offer->user_data = user_data; + + display_watch_fd(offer->input->display, + offer->fd, EPOLLIN, &offer->io_task); +} + +void +input_receive_drag_data(struct input *input, const char *mime_type, + data_func_t func, void *data) +{ + data_offer_receive_data(input->drag_offer, mime_type, func, data); + input->drag_offer->x = input->sx; + input->drag_offer->y = input->sy; +} + +int +input_receive_drag_data_to_fd(struct input *input, + const char *mime_type, int fd) +{ + if (input->drag_offer) + wl_data_offer_receive(input->drag_offer->offer, mime_type, fd); + + return 0; +} + +int +input_receive_selection_data(struct input *input, const char *mime_type, + data_func_t func, void *data) +{ + char **p; + + if (input->selection_offer == NULL) + return -1; + + for (p = input->selection_offer->types.data; *p; p++) + if (strcmp(mime_type, *p) == 0) + break; + + if (*p == NULL) + return -1; + + data_offer_receive_data(input->selection_offer, + mime_type, func, data); + return 0; +} + +int +input_receive_selection_data_to_fd(struct input *input, + const char *mime_type, int fd) +{ + if (input->selection_offer) + wl_data_offer_receive(input->selection_offer->offer, + mime_type, fd); + + return 0; +} + +void +window_move(struct window *window, struct input *input, uint32_t serial) +{ + if (!window->shell_surface) + return; + + wl_shell_surface_move(window->shell_surface, input->seat, serial); +} + +void +window_touch_move(struct window *window, struct input *input, uint32_t serial) +{ + if (!window->shell_surface) + return; + + wl_shell_surface_move(window->shell_surface, input->seat, + window->display->serial); +} + +static void +surface_set_synchronized(struct surface *surface) +{ + if (!surface->subsurface) + return; + + if (surface->synchronized) + return; + + wl_subsurface_set_sync(surface->subsurface); + surface->synchronized = 1; +} + +static void +surface_set_synchronized_default(struct surface *surface) +{ + if (!surface->subsurface) + return; + + if (surface->synchronized == surface->synchronized_default) + return; + + if (surface->synchronized_default) + wl_subsurface_set_sync(surface->subsurface); + else + wl_subsurface_set_desync(surface->subsurface); + + surface->synchronized = surface->synchronized_default; +} + +static void +surface_resize(struct surface *surface) +{ + struct widget *widget = surface->widget; + struct wl_compositor *compositor = widget->window->display->compositor; + + if (surface->input_region) { + wl_region_destroy(surface->input_region); + surface->input_region = NULL; + } + + if (surface->opaque_region) + wl_region_destroy(surface->opaque_region); + + surface->opaque_region = wl_compositor_create_region(compositor); + + if (widget->resize_handler) + widget->resize_handler(widget, + widget->allocation.width, + widget->allocation.height, + widget->user_data); + + if (surface->subsurface && + (surface->allocation.x != widget->allocation.x || + surface->allocation.y != widget->allocation.y)) { + wl_subsurface_set_position(surface->subsurface, + widget->allocation.x, + widget->allocation.y); + } + if (surface->allocation.width != widget->allocation.width || + surface->allocation.height != widget->allocation.height) { + window_schedule_redraw(widget->window); + } + surface->allocation = widget->allocation; + + if (widget->opaque) + wl_region_add(surface->opaque_region, 0, 0, + widget->allocation.width, + widget->allocation.height); +} + +static void +hack_prevent_EGL_sub_surface_deadlock(struct window *window) +{ + /* + * This hack should be removed, when EGL respects + * eglSwapInterval(0). + * + * If this window has sub-surfaces, especially a free-running + * EGL-widget, we need to post the parent surface once with + * all the old state to guarantee, that the EGL-widget will + * receive its frame callback soon. Otherwise, a forced call + * to eglSwapBuffers may end up blocking, waiting for a frame + * event that will never come, because we will commit the parent + * surface with all new state only after eglSwapBuffers returns. + * + * This assumes, that: + * 1. When the EGL widget's resize hook is called, it pauses. + * 2. When the EGL widget's redraw hook is called, it forces a + * repaint and a call to eglSwapBuffers(), and maybe resumes. + * In a single threaded application condition 1 is a no-op. + * + * XXX: This should actually be after the surface_resize() calls, + * but cannot, because then it would commit the incomplete state + * accumulated from the widget resize hooks. + */ + if (window->subsurface_list.next != &window->main_surface->link || + window->subsurface_list.prev != &window->main_surface->link) + wl_surface_commit(window->main_surface->surface); +} + +static void +idle_resize(struct window *window) +{ + struct surface *surface; + + window->resize_needed = 0; + window->redraw_needed = 1; + + DBG("from %dx%d to %dx%d\n", + window->main_surface->server_allocation.width, + window->main_surface->server_allocation.height, + window->pending_allocation.width, + window->pending_allocation.height); + + hack_prevent_EGL_sub_surface_deadlock(window); + + widget_set_allocation(window->main_surface->widget, + window->pending_allocation.x, + window->pending_allocation.y, + window->pending_allocation.width, + window->pending_allocation.height); + + surface_resize(window->main_surface); + + /* The main surface is in the list, too. Main surface's + * resize_handler is responsible for calling widget_set_allocation() + * on all sub-surface root widgets, so they will be resized + * properly. + */ + wl_list_for_each(surface, &window->subsurface_list, link) { + if (surface == window->main_surface) + continue; + + surface_set_synchronized(surface); + surface_resize(surface); + } +} + +void +window_schedule_resize(struct window *window, int width, int height) +{ + /* We should probably get these numbers from the theme. */ + const int min_width = 200, min_height = 200; + + window->pending_allocation.x = 0; + window->pending_allocation.y = 0; + window->pending_allocation.width = width; + window->pending_allocation.height = height; + + if (window->min_allocation.width == 0) { + if (width < min_width && window->frame) + window->min_allocation.width = min_width; + else + window->min_allocation.width = width; + if (height < min_height && window->frame) + window->min_allocation.height = min_height; + else + window->min_allocation.height = height; + } + + if (window->pending_allocation.width < window->min_allocation.width) + window->pending_allocation.width = window->min_allocation.width; + if (window->pending_allocation.height < window->min_allocation.height) + window->pending_allocation.height = window->min_allocation.height; + + window->resize_needed = 1; + window_schedule_redraw(window); +} + +void +widget_schedule_resize(struct widget *widget, int32_t width, int32_t height) +{ + window_schedule_resize(widget->window, width, height); +} + +static void +handle_ping(void *data, struct wl_shell_surface *shell_surface, + uint32_t serial) +{ + wl_shell_surface_pong(shell_surface, serial); +} + +static void +handle_configure(void *data, struct wl_shell_surface *shell_surface, + uint32_t edges, int32_t width, int32_t height) +{ + struct window *window = data; + + window->resize_edges = edges; + window_schedule_resize(window, width, height); +} + +static void +menu_destroy(struct menu *menu) +{ + widget_destroy(menu->widget); + window_destroy(menu->window); + free(menu); +} + +static void +handle_popup_done(void *data, struct wl_shell_surface *shell_surface) +{ + struct window *window = data; + struct menu *menu = window->main_surface->widget->user_data; + + /* FIXME: Need more context in this event, at least the input + * device. Or just use wl_callback. And this really needs to + * be a window vfunc that the menu can set. And we need the + * time. */ + + input_ungrab(menu->input); + menu_destroy(menu); +} + +static const struct wl_shell_surface_listener shell_surface_listener = { + handle_ping, + handle_configure, + handle_popup_done +}; + +void +window_get_allocation(struct window *window, + struct rectangle *allocation) +{ + *allocation = window->main_surface->allocation; +} + +static void +widget_redraw(struct widget *widget) +{ + struct widget *child; + + if (widget->redraw_handler) + widget->redraw_handler(widget, widget->user_data); + wl_list_for_each(child, &widget->child_list, link) + widget_redraw(child); +} + +static void +frame_callback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct surface *surface = data; + + assert(callback == surface->frame_cb); + DBG_OBJ(callback, "done\n"); + wl_callback_destroy(callback); + surface->frame_cb = NULL; + + surface->last_time = time; + + if (surface->redraw_needed || surface->window->redraw_needed) { + DBG_OBJ(surface->surface, "window_schedule_redraw_task\n"); + window_schedule_redraw_task(surface->window); + } +} + +static const struct wl_callback_listener listener = { + frame_callback +}; + +static void +surface_redraw(struct surface *surface) +{ + DBG_OBJ(surface->surface, "begin\n"); + + if (!surface->window->redraw_needed && !surface->redraw_needed) + return; + + /* Whole-window redraw forces a redraw even if the previous has + * not yet hit the screen. + */ + if (surface->frame_cb) { + if (!surface->window->redraw_needed) + return; + + DBG_OBJ(surface->frame_cb, "cancelled\n"); + wl_callback_destroy(surface->frame_cb); + } + + surface->frame_cb = wl_surface_frame(surface->surface); + wl_callback_add_listener(surface->frame_cb, &listener, surface); + DBG_OBJ(surface->frame_cb, "new\n"); + + surface->redraw_needed = 0; + DBG_OBJ(surface->surface, "-> widget_redraw\n"); + widget_redraw(surface->widget); + DBG_OBJ(surface->surface, "done\n"); +} + +static void +idle_redraw(struct task *task, uint32_t events) +{ + struct window *window = container_of(task, struct window, redraw_task); + struct surface *surface; + + DBG(" --------- \n"); + + wl_list_init(&window->redraw_task.link); + window->redraw_task_scheduled = 0; + + if (window->resize_needed) { + /* throttle resizing to the main surface display */ + if (window->main_surface->frame_cb) { + DBG_OBJ(window->main_surface->frame_cb, "pending\n"); + return; + } + + idle_resize(window); + } + + wl_list_for_each(surface, &window->subsurface_list, link) + surface_redraw(surface); + + window->redraw_needed = 0; + window_flush(window); + + wl_list_for_each(surface, &window->subsurface_list, link) + surface_set_synchronized_default(surface); +} + +static void +window_schedule_redraw_task(struct window *window) +{ + if (window->configure_requests) + return; + if (!window->redraw_task_scheduled) { + window->redraw_task.run = idle_redraw; + display_defer(window->display, &window->redraw_task); + window->redraw_task_scheduled = 1; + } +} + +void +window_schedule_redraw(struct window *window) +{ + struct surface *surface; + + DBG_OBJ(window->main_surface->surface, "window %p\n", window); + + wl_list_for_each(surface, &window->subsurface_list, link) + surface->redraw_needed = 1; + + window_schedule_redraw_task(window); +} + +int +window_is_fullscreen(struct window *window) +{ + return window->type == TYPE_FULLSCREEN; +} + +static void +configure_request_completed(void *data, struct wl_callback *callback, uint32_t time) +{ + struct window *window = data; + + wl_callback_destroy(callback); + window->configure_requests--; + + if (!window->configure_requests) + window_schedule_redraw(window); +} + +static struct wl_callback_listener configure_request_listener = { + configure_request_completed, +}; + +static void +window_defer_redraw_until_configure(struct window* window) +{ + struct wl_callback *callback; + + if (window->redraw_task_scheduled) { + wl_list_remove(&window->redraw_task.link); + window->redraw_task_scheduled = 0; + } + + callback = wl_display_sync(window->display->display); + wl_callback_add_listener(callback, &configure_request_listener, window); + window->configure_requests++; +} + +void +window_set_fullscreen(struct window *window, int fullscreen) +{ + if (!window->display->shell) + return; + + if ((window->type == TYPE_FULLSCREEN) == fullscreen) + return; + + if (fullscreen) { + window->saved_type = window->type; + if (window->type == TYPE_TOPLEVEL) { + window->saved_allocation = window->main_surface->allocation; + } + window->type = TYPE_FULLSCREEN; + wl_shell_surface_set_fullscreen(window->shell_surface, + window->fullscreen_method, + 0, NULL); + window_defer_redraw_until_configure (window); + } else { + if (window->saved_type == TYPE_MAXIMIZED) { + window_set_maximized(window, 1); + } else { + window->type = TYPE_TOPLEVEL; + wl_shell_surface_set_toplevel(window->shell_surface); + window_schedule_resize(window, + window->saved_allocation.width, + window->saved_allocation.height); + } + + } +} + +void +window_set_fullscreen_method(struct window *window, + enum wl_shell_surface_fullscreen_method method) +{ + window->fullscreen_method = method; +} + +int +window_is_maximized(struct window *window) +{ + return window->type == TYPE_MAXIMIZED; +} + +void +window_set_maximized(struct window *window, int maximized) +{ + if (!window->display->shell) + return; + + if ((window->type == TYPE_MAXIMIZED) == maximized) + return; + + if (window->type == TYPE_TOPLEVEL) { + window->saved_allocation = window->main_surface->allocation; + wl_shell_surface_set_maximized(window->shell_surface, NULL); + window->type = TYPE_MAXIMIZED; + window_defer_redraw_until_configure(window); + } else if (window->type == TYPE_FULLSCREEN) { + wl_shell_surface_set_maximized(window->shell_surface, NULL); + window->type = TYPE_MAXIMIZED; + window_defer_redraw_until_configure(window); + } else { + wl_shell_surface_set_toplevel(window->shell_surface); + window->type = TYPE_TOPLEVEL; + window_schedule_resize(window, + window->saved_allocation.width, + window->saved_allocation.height); + } +} + +void +window_set_user_data(struct window *window, void *data) +{ + window->user_data = data; +} + +void * +window_get_user_data(struct window *window) +{ + return window->user_data; +} + +void +window_set_key_handler(struct window *window, + window_key_handler_t handler) +{ + window->key_handler = handler; +} + +void +window_set_keyboard_focus_handler(struct window *window, + window_keyboard_focus_handler_t handler) +{ + window->keyboard_focus_handler = handler; +} + +void +window_set_data_handler(struct window *window, window_data_handler_t handler) +{ + window->data_handler = handler; +} + +void +window_set_drop_handler(struct window *window, window_drop_handler_t handler) +{ + window->drop_handler = handler; +} + +void +window_set_close_handler(struct window *window, + window_close_handler_t handler) +{ + window->close_handler = handler; +} + +void +window_set_fullscreen_handler(struct window *window, + window_fullscreen_handler_t handler) +{ + window->fullscreen_handler = handler; +} + +void +window_set_output_handler(struct window *window, + window_output_handler_t handler) +{ + window->output_handler = handler; +} + +void +window_set_title(struct window *window, const char *title) +{ + free(window->title); + window->title = strdup(title); + if (window->shell_surface) + wl_shell_surface_set_title(window->shell_surface, title); +} + +const char * +window_get_title(struct window *window) +{ + return window->title; +} + +void +window_set_text_cursor_position(struct window *window, int32_t x, int32_t y) +{ + struct text_cursor_position *text_cursor_position = + window->display->text_cursor_position; + + if (!text_cursor_position) + return; + + text_cursor_position_notify(text_cursor_position, + window->main_surface->surface, + wl_fixed_from_int(x), + wl_fixed_from_int(y)); +} + +void +window_damage(struct window *window, int32_t x, int32_t y, + int32_t width, int32_t height) +{ + wl_surface_damage(window->main_surface->surface, x, y, width, height); +} + +static void +surface_enter(void *data, + struct wl_surface *wl_surface, struct wl_output *wl_output) +{ + struct window *window = data; + struct output *output; + struct output *output_found = NULL; + struct window_output *window_output; + + wl_list_for_each(output, &window->display->output_list, link) { + if (output->output == wl_output) { + output_found = output; + break; + } + } + + if (!output_found) + return; + + window_output = xmalloc(sizeof *window_output); + window_output->output = output_found; + + wl_list_insert (&window->window_output_list, &window_output->link); + + if (window->output_handler) + window->output_handler(window, output_found, 1, + window->user_data); +} + +static void +surface_leave(void *data, + struct wl_surface *wl_surface, struct wl_output *output) +{ + struct window *window = data; + struct window_output *window_output; + struct window_output *window_output_found = NULL; + + wl_list_for_each(window_output, &window->window_output_list, link) { + if (window_output->output->output == output) { + window_output_found = window_output; + break; + } + } + + if (window_output_found) { + wl_list_remove(&window_output_found->link); + + if (window->output_handler) + window->output_handler(window, window_output->output, + 0, window->user_data); + + free(window_output_found); + } +} + +static const struct wl_surface_listener surface_listener = { + surface_enter, + surface_leave +}; + +static struct surface * +surface_create(struct window *window) +{ + struct display *display = window->display; + struct surface *surface; + + surface = xmalloc(sizeof *surface); + memset(surface, 0, sizeof *surface); + if (!surface) + return NULL; + + surface->window = window; + surface->surface = wl_compositor_create_surface(display->compositor); + surface->buffer_scale = 1; + wl_surface_add_listener(surface->surface, &surface_listener, window); + + wl_list_insert(&window->subsurface_list, &surface->link); + + return surface; +} + +static struct window * +window_create_internal(struct display *display, + struct window *parent, int type) +{ + struct window *window; + struct surface *surface; + + window = xzalloc(sizeof *window); + wl_list_init(&window->subsurface_list); + window->display = display; + window->parent = parent; + + surface = surface_create(window); + window->main_surface = surface; + + if (type != TYPE_CUSTOM && display->shell) { + window->shell_surface = + wl_shell_get_shell_surface(display->shell, + surface->surface); + fail_on_null(window->shell_surface); + } + + window->type = type; + window->fullscreen_method = WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT; + window->configure_requests = 0; + window->preferred_format = WINDOW_PREFERRED_FORMAT_NONE; + + if (display->argb_device) +#ifdef HAVE_CAIRO_EGL + surface->buffer_type = WINDOW_BUFFER_TYPE_EGL_WINDOW; +#else + surface->buffer_type = WINDOW_BUFFER_TYPE_SHM; +#endif + else + surface->buffer_type = WINDOW_BUFFER_TYPE_SHM; + + wl_surface_set_user_data(surface->surface, window); + wl_list_insert(display->window_list.prev, &window->link); + wl_list_init(&window->redraw_task.link); + + if (window->shell_surface) { + wl_shell_surface_set_user_data(window->shell_surface, window); + wl_shell_surface_add_listener(window->shell_surface, + &shell_surface_listener, window); + } + + wl_list_init (&window->window_output_list); + + return window; +} + +struct window * +window_create(struct display *display) +{ + return window_create_internal(display, NULL, TYPE_NONE); +} + +struct window * +window_create_custom(struct display *display) +{ + return window_create_internal(display, NULL, TYPE_CUSTOM); +} + +struct window * +window_create_transient(struct display *display, struct window *parent, + int32_t x, int32_t y, uint32_t flags) +{ + struct window *window; + + window = window_create_internal(parent->display, + parent, TYPE_TRANSIENT); + + window->x = x; + window->y = y; + + if (display->shell) + wl_shell_surface_set_transient( + window->shell_surface, + window->parent->main_surface->surface, + window->x, window->y, flags); + + return window; +} + +static void +menu_set_item(struct menu *menu, int sy) +{ + int next; + + next = (sy - 8) / 20; + if (menu->current != next) { + menu->current = next; + widget_schedule_redraw(menu->widget); + } +} + +static int +menu_motion_handler(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data) +{ + struct menu *menu = data; + + if (widget == menu->widget) + menu_set_item(data, y); + + return CURSOR_LEFT_PTR; +} + +static int +menu_enter_handler(struct widget *widget, + struct input *input, float x, float y, void *data) +{ + struct menu *menu = data; + + if (widget == menu->widget) + menu_set_item(data, y); + + return CURSOR_LEFT_PTR; +} + +static void +menu_leave_handler(struct widget *widget, struct input *input, void *data) +{ + struct menu *menu = data; + + if (widget == menu->widget) + menu_set_item(data, -200); +} + +static void +menu_button_handler(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, enum wl_pointer_button_state state, + void *data) + +{ + struct menu *menu = data; + + if (state == WL_POINTER_BUTTON_STATE_RELEASED && + (menu->release_count > 0 || time - menu->time > 500)) { + /* Either relase after press-drag-release or + * click-motion-click. */ + menu->func(menu->window->parent, + menu->current, menu->window->parent->user_data); + input_ungrab(input); + menu_destroy(menu); + } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { + menu->release_count++; + } +} + +static void +menu_redraw_handler(struct widget *widget, void *data) +{ + cairo_t *cr; + const int32_t r = 3, margin = 3; + struct menu *menu = data; + int32_t width, height, i; + + cr = widget_cairo_create(widget); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); + cairo_paint(cr); + + width = widget->allocation.width; + height = widget->allocation.height; + rounded_rect(cr, 0, 0, width, height, r); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.4, 0.8); + cairo_fill(cr); + + for (i = 0; i < menu->count; i++) { + if (i == menu->current) { + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_rectangle(cr, margin, i * 20 + margin, + width - 2 * margin, 20); + cairo_fill(cr); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_move_to(cr, 10, i * 20 + 16); + cairo_show_text(cr, menu->entries[i]); + } else { + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_move_to(cr, 10, i * 20 + 16); + cairo_show_text(cr, menu->entries[i]); + } + } + + cairo_destroy(cr); +} + +void +window_show_menu(struct display *display, + struct input *input, uint32_t time, struct window *parent, + int32_t x, int32_t y, + menu_func_t func, const char **entries, int count) +{ + struct window *window; + struct menu *menu; + const int32_t margin = 3; + + menu = malloc(sizeof *menu); + if (!menu) + return; + + window = window_create_internal(parent->display, parent, TYPE_MENU); + if (!window) { + free(menu); + return; + } + + menu->window = window; + menu->widget = window_add_widget(menu->window, menu); + window_set_buffer_scale (menu->window, window_get_buffer_scale (parent)); + window_set_buffer_transform (menu->window, window_get_buffer_transform (parent)); + menu->entries = entries; + menu->count = count; + menu->release_count = 0; + menu->current = -1; + menu->time = time; + menu->func = func; + menu->input = input; + window->type = TYPE_MENU; + window->x = x; + window->y = y; + + input_ungrab(input); + wl_shell_surface_set_popup(window->shell_surface, input->seat, + display_get_serial(window->display), + window->parent->main_surface->surface, + window->x, window->y, 0); + + widget_set_redraw_handler(menu->widget, menu_redraw_handler); + widget_set_enter_handler(menu->widget, menu_enter_handler); + widget_set_leave_handler(menu->widget, menu_leave_handler); + widget_set_motion_handler(menu->widget, menu_motion_handler); + widget_set_button_handler(menu->widget, menu_button_handler); + + input_grab(input, menu->widget, 0); + window_schedule_resize(window, 200, count * 20 + margin * 2); +} + +void +window_set_buffer_type(struct window *window, enum window_buffer_type type) +{ + window->main_surface->buffer_type = type; +} + +void +window_set_preferred_format(struct window *window, + enum preferred_format format) +{ + window->preferred_format = format; +} + +struct widget * +window_add_subsurface(struct window *window, void *data, + enum subsurface_mode default_mode) +{ + struct widget *widget; + struct surface *surface; + struct wl_surface *parent; + struct wl_subcompositor *subcompo = window->display->subcompositor; + + surface = surface_create(window); + widget = widget_create(window, surface, data); + wl_list_init(&widget->link); + surface->widget = widget; + + parent = window->main_surface->surface; + surface->subsurface = wl_subcompositor_get_subsurface(subcompo, + surface->surface, + parent); + surface->synchronized = 1; + + switch (default_mode) { + case SUBSURFACE_SYNCHRONIZED: + surface->synchronized_default = 1; + break; + case SUBSURFACE_DESYNCHRONIZED: + surface->synchronized_default = 0; + break; + default: + assert(!"bad enum subsurface_mode"); + } + + return widget; +} + +static void +display_handle_geometry(void *data, + struct wl_output *wl_output, + int x, int y, + int physical_width, + int physical_height, + int subpixel, + const char *make, + const char *model, + int transform) +{ + struct output *output = data; + + output->allocation.x = x; + output->allocation.y = y; + output->transform = transform; +} + +static void +display_handle_done(void *data, + struct wl_output *wl_output) +{ +} + +static void +display_handle_scale(void *data, + struct wl_output *wl_output, + int32_t scale) +{ + struct output *output = data; + + output->scale = scale; +} + +static void +display_handle_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int width, + int height, + int refresh) +{ + struct output *output = data; + struct display *display = output->display; + + if (flags & WL_OUTPUT_MODE_CURRENT) { + output->allocation.width = width; + output->allocation.height = height; + if (display->output_configure_handler) + (*display->output_configure_handler)( + output, display->user_data); + } +} + +static const struct wl_output_listener output_listener = { + display_handle_geometry, + display_handle_mode, + display_handle_done, + display_handle_scale +}; + +static void +display_add_output(struct display *d, uint32_t id) +{ + struct output *output; + + output = xzalloc(sizeof *output); + output->display = d; + output->scale = 1; + output->output = + wl_registry_bind(d->registry, id, &wl_output_interface, 2); + wl_list_insert(d->output_list.prev, &output->link); + + wl_output_add_listener(output->output, &output_listener, output); +} + +static void +output_destroy(struct output *output) +{ + if (output->destroy_handler) + (*output->destroy_handler)(output, output->user_data); + + wl_output_destroy(output->output); + wl_list_remove(&output->link); + free(output); +} + +void +display_set_global_handler(struct display *display, + display_global_handler_t handler) +{ + struct global *global; + + display->global_handler = handler; + if (!handler) + return; + + wl_list_for_each(global, &display->global_list, link) + display->global_handler(display, + global->name, global->interface, + global->version, display->user_data); +} + +void +display_set_output_configure_handler(struct display *display, + display_output_handler_t handler) +{ + struct output *output; + + display->output_configure_handler = handler; + if (!handler) + return; + + wl_list_for_each(output, &display->output_list, link) { + if (output->allocation.width == 0 && + output->allocation.height == 0) + continue; + + (*display->output_configure_handler)(output, + display->user_data); + } +} + +void +output_set_user_data(struct output *output, void *data) +{ + output->user_data = data; +} + +void * +output_get_user_data(struct output *output) +{ + return output->user_data; +} + +void +output_set_destroy_handler(struct output *output, + display_output_handler_t handler) +{ + output->destroy_handler = handler; + /* FIXME: implement this, once we have way to remove outputs */ +} + +void +output_get_allocation(struct output *output, struct rectangle *base) +{ + struct rectangle allocation = output->allocation; + + switch (output->transform) { + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + /* Swap width and height */ + allocation.width = output->allocation.height; + allocation.height = output->allocation.width; + break; + } + + *base = allocation; +} + +struct wl_output * +output_get_wl_output(struct output *output) +{ + return output->output; +} + +enum wl_output_transform +output_get_transform(struct output *output) +{ + return output->transform; +} + +uint32_t +output_get_scale(struct output *output) +{ + return output->scale; +} + +static void +fini_xkb(struct input *input) +{ + xkb_state_unref(input->xkb.state); + xkb_map_unref(input->xkb.keymap); +} + +#define MAX(a,b) ((a) > (b) ? a : b) + +static void +display_add_input(struct display *d, uint32_t id) +{ + struct input *input; + + input = xzalloc(sizeof *input); + input->display = d; + input->seat = wl_registry_bind(d->registry, id, &wl_seat_interface, + MAX(d->seat_version, 3)); + input->touch_focus = NULL; + input->pointer_focus = NULL; + input->keyboard_focus = NULL; + wl_list_init(&input->touch_point_list); + wl_list_insert(d->input_list.prev, &input->link); + + wl_seat_add_listener(input->seat, &seat_listener, input); + wl_seat_set_user_data(input->seat, input); + + input->data_device = + wl_data_device_manager_get_data_device(d->data_device_manager, + input->seat); + wl_data_device_add_listener(input->data_device, &data_device_listener, + input); + + input->pointer_surface = wl_compositor_create_surface(d->compositor); + + input->repeat_timer_fd = timerfd_create(CLOCK_MONOTONIC, + TFD_CLOEXEC | TFD_NONBLOCK); + input->repeat_task.run = keyboard_repeat_func; + display_watch_fd(d, input->repeat_timer_fd, + EPOLLIN, &input->repeat_task); +} + +static void +input_destroy(struct input *input) +{ + input_remove_keyboard_focus(input); + input_remove_pointer_focus(input); + + if (input->drag_offer) + data_offer_destroy(input->drag_offer); + + if (input->selection_offer) + data_offer_destroy(input->selection_offer); + + wl_data_device_destroy(input->data_device); + + if (input->display->seat_version >= 3) { + if (input->pointer) + wl_pointer_release(input->pointer); + if (input->keyboard) + wl_keyboard_release(input->keyboard); + } + + fini_xkb(input); + + wl_surface_destroy(input->pointer_surface); + + wl_list_remove(&input->link); + wl_seat_destroy(input->seat); + close(input->repeat_timer_fd); + free(input); +} + +static void +init_workspace_manager(struct display *d, uint32_t id) +{ + d->workspace_manager = + wl_registry_bind(d->registry, id, + &workspace_manager_interface, 1); + if (d->workspace_manager != NULL) + workspace_manager_add_listener(d->workspace_manager, + &workspace_manager_listener, + d); +} + +static void +shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct display *d = data; + + if (format == WL_SHM_FORMAT_RGB565) + d->has_rgb565 = 1; +} + +struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +registry_handle_global(void *data, struct wl_registry *registry, uint32_t id, + const char *interface, uint32_t version) +{ + struct display *d = data; + struct global *global; + + global = xmalloc(sizeof *global); + global->name = id; + global->interface = strdup(interface); + global->version = version; + wl_list_insert(d->global_list.prev, &global->link); + + if (strcmp(interface, "wl_compositor") == 0) { + d->compositor = wl_registry_bind(registry, id, + &wl_compositor_interface, 3); + } else if (strcmp(interface, "wl_output") == 0) { + display_add_output(d, id); + } else if (strcmp(interface, "wl_seat") == 0) { + d->seat_version = version; + display_add_input(d, id); + } else if (strcmp(interface, "wl_shell") == 0) { + d->shell = wl_registry_bind(registry, + id, &wl_shell_interface, 1); + } else if (strcmp(interface, "wl_shm") == 0) { + d->shm = wl_registry_bind(registry, id, &wl_shm_interface, 1); + wl_shm_add_listener(d->shm, &shm_listener, d); + } else if (strcmp(interface, "wl_data_device_manager") == 0) { + d->data_device_manager = + wl_registry_bind(registry, id, + &wl_data_device_manager_interface, 1); + } else if (strcmp(interface, "text_cursor_position") == 0) { + d->text_cursor_position = + wl_registry_bind(registry, id, + &text_cursor_position_interface, 1); + } else if (strcmp(interface, "workspace_manager") == 0) { + init_workspace_manager(d, id); + } else if (strcmp(interface, "wl_subcompositor") == 0) { + d->subcompositor = + wl_registry_bind(registry, id, + &wl_subcompositor_interface, 1); + } + + if (d->global_handler) + d->global_handler(d, id, interface, version, d->user_data); +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ + struct display *d = data; + struct global *global; + struct global *tmp; + + wl_list_for_each_safe(global, tmp, &d->global_list, link) { + if (global->name != name) + continue; + + /* XXX: Should destroy bound globals, and call + * the counterpart of display::global_handler + */ + wl_list_remove(&global->link); + free(global->interface); + free(global); + } +} + +void * +display_bind(struct display *display, uint32_t name, + const struct wl_interface *interface, uint32_t version) +{ + return wl_registry_bind(display->registry, name, interface, version); +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +#ifdef HAVE_CAIRO_EGL +static int +init_egl(struct display *d) +{ + EGLint major, minor; + EGLint n; + +#ifdef USE_CAIRO_GLESV2 +# define GL_BIT EGL_OPENGL_ES2_BIT +#else +# define GL_BIT EGL_OPENGL_BIT +#endif + + static const EGLint argb_cfg_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_DEPTH_SIZE, 1, + EGL_RENDERABLE_TYPE, GL_BIT, + EGL_NONE + }; + +#ifdef USE_CAIRO_GLESV2 + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + EGLint api = EGL_OPENGL_ES_API; +#else + EGLint *context_attribs = NULL; + EGLint api = EGL_OPENGL_API; +#endif + + d->dpy = eglGetDisplay(d->display); + if (!eglInitialize(d->dpy, &major, &minor)) { + fprintf(stderr, "failed to initialize EGL\n"); + return -1; + } + + if (!eglBindAPI(api)) { + fprintf(stderr, "failed to bind EGL client API\n"); + return -1; + } + + if (!eglChooseConfig(d->dpy, argb_cfg_attribs, + &d->argb_config, 1, &n) || n != 1) { + fprintf(stderr, "failed to choose argb EGL config\n"); + return -1; + } + + d->argb_ctx = eglCreateContext(d->dpy, d->argb_config, + EGL_NO_CONTEXT, context_attribs); + if (d->argb_ctx == NULL) { + fprintf(stderr, "failed to create EGL context\n"); + return -1; + } + + d->argb_device = cairo_egl_device_create(d->dpy, d->argb_ctx); + if (cairo_device_status(d->argb_device) != CAIRO_STATUS_SUCCESS) { + fprintf(stderr, "failed to get cairo EGL argb device\n"); + return -1; + } + + return 0; +} + +static void +fini_egl(struct display *display) +{ + cairo_device_destroy(display->argb_device); + + eglMakeCurrent(display->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + + eglTerminate(display->dpy); + eglReleaseThread(); +} +#endif + +static void +init_dummy_surface(struct display *display) +{ + int len; + void *data; + + len = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, 1); + data = malloc(len); + display->dummy_surface = + cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, + 1, 1, len); + display->dummy_surface_data = data; +} + +static void +handle_display_data(struct task *task, uint32_t events) +{ + struct display *display = + container_of(task, struct display, display_task); + struct epoll_event ep; + int ret; + + display->display_fd_events = events; + + if (events & EPOLLERR || events & EPOLLHUP) { + display_exit(display); + return; + } + + if (events & EPOLLIN) { + ret = wl_display_dispatch(display->display); + if (ret == -1) { + display_exit(display); + return; + } + } + + if (events & EPOLLOUT) { + ret = wl_display_flush(display->display); + if (ret == 0) { + ep.events = EPOLLIN | EPOLLERR | EPOLLHUP; + ep.data.ptr = &display->display_task; + epoll_ctl(display->epoll_fd, EPOLL_CTL_MOD, + display->display_fd, &ep); + } else if (ret == -1 && errno != EAGAIN) { + display_exit(display); + return; + } + } +} + +static void +log_handler(const char *format, va_list args) +{ + vfprintf(stderr, format, args); +} + +struct display * +display_create(int *argc, char *argv[]) +{ + struct display *d; + + wl_log_set_handler_client(log_handler); + + d = zalloc(sizeof *d); + if (d == NULL) + return NULL; + + d->display = wl_display_connect(NULL); + if (d->display == NULL) { + fprintf(stderr, "failed to connect to Wayland display: %m\n"); + free(d); + return NULL; + } + + d->xkb_context = xkb_context_new(0); + if (d->xkb_context == NULL) { + fprintf(stderr, "Failed to create XKB context\n"); + free(d); + return NULL; + } + + d->epoll_fd = os_epoll_create_cloexec(); + d->display_fd = wl_display_get_fd(d->display); + d->display_task.run = handle_display_data; + display_watch_fd(d, d->display_fd, EPOLLIN | EPOLLERR | EPOLLHUP, + &d->display_task); + + wl_list_init(&d->deferred_list); + wl_list_init(&d->input_list); + wl_list_init(&d->output_list); + wl_list_init(&d->global_list); + + d->workspace = 0; + d->workspace_count = 1; + + d->registry = wl_display_get_registry(d->display); + wl_registry_add_listener(d->registry, ®istry_listener, d); + + if (wl_display_dispatch(d->display) < 0) { + fprintf(stderr, "Failed to process Wayland connection: %m\n"); + return NULL; + } + +#ifdef HAVE_CAIRO_EGL + if (init_egl(d) < 0) + fprintf(stderr, "EGL does not seem to work, " + "falling back to software rendering and wl_shm.\n"); +#endif + + create_cursors(d); + + d->theme = theme_create(); + + wl_list_init(&d->window_list); + + init_dummy_surface(d); + + return d; +} + +static void +display_destroy_outputs(struct display *display) +{ + struct output *tmp; + struct output *output; + + wl_list_for_each_safe(output, tmp, &display->output_list, link) + output_destroy(output); +} + +static void +display_destroy_inputs(struct display *display) +{ + struct input *tmp; + struct input *input; + + wl_list_for_each_safe(input, tmp, &display->input_list, link) + input_destroy(input); +} + +void +display_destroy(struct display *display) +{ + if (!wl_list_empty(&display->window_list)) + fprintf(stderr, "toytoolkit warning: %d windows exist.\n", + wl_list_length(&display->window_list)); + + if (!wl_list_empty(&display->deferred_list)) + fprintf(stderr, "toytoolkit warning: deferred tasks exist.\n"); + + cairo_surface_destroy(display->dummy_surface); + free(display->dummy_surface_data); + + display_destroy_outputs(display); + display_destroy_inputs(display); + + xkb_context_unref(display->xkb_context); + + theme_destroy(display->theme); + destroy_cursors(display); + +#ifdef HAVE_CAIRO_EGL + if (display->argb_device) + fini_egl(display); +#endif + + if (display->subcompositor) + wl_subcompositor_destroy(display->subcompositor); + + if (display->shell) + wl_shell_destroy(display->shell); + + if (display->shm) + wl_shm_destroy(display->shm); + + if (display->data_device_manager) + wl_data_device_manager_destroy(display->data_device_manager); + + wl_compositor_destroy(display->compositor); + wl_registry_destroy(display->registry); + + close(display->epoll_fd); + + if (!(display->display_fd_events & EPOLLERR) && + !(display->display_fd_events & EPOLLHUP)) + wl_display_flush(display->display); + + wl_display_disconnect(display->display); + free(display); +} + +void +display_set_user_data(struct display *display, void *data) +{ + display->user_data = data; +} + +void * +display_get_user_data(struct display *display) +{ + return display->user_data; +} + +struct wl_display * +display_get_display(struct display *display) +{ + return display->display; +} + +int +display_has_subcompositor(struct display *display) +{ + if (display->subcompositor) + return 1; + + wl_display_roundtrip(display->display); + + return display->subcompositor != NULL; +} + +cairo_device_t * +display_get_cairo_device(struct display *display) +{ + return display->argb_device; +} + +struct output * +display_get_output(struct display *display) +{ + return container_of(display->output_list.next, struct output, link); +} + +struct wl_compositor * +display_get_compositor(struct display *display) +{ + return display->compositor; +} + +uint32_t +display_get_serial(struct display *display) +{ + return display->serial; +} + +EGLDisplay +display_get_egl_display(struct display *d) +{ + return d->dpy; +} + +struct wl_data_source * +display_create_data_source(struct display *display) +{ + return wl_data_device_manager_create_data_source(display->data_device_manager); +} + +EGLConfig +display_get_argb_egl_config(struct display *d) +{ + return d->argb_config; +} + +struct wl_shell * +display_get_shell(struct display *display) +{ + return display->shell; +} + +int +display_acquire_window_surface(struct display *display, + struct window *window, + EGLContext ctx) +{ + struct surface *surface = window->main_surface; + + if (surface->buffer_type != WINDOW_BUFFER_TYPE_EGL_WINDOW) + return -1; + + widget_get_cairo_surface(window->main_surface->widget); + return surface->toysurface->acquire(surface->toysurface, ctx); +} + +void +display_release_window_surface(struct display *display, + struct window *window) +{ + struct surface *surface = window->main_surface; + + if (surface->buffer_type != WINDOW_BUFFER_TYPE_EGL_WINDOW) + return; + + surface->toysurface->release(surface->toysurface); +} + +void +display_defer(struct display *display, struct task *task) +{ + wl_list_insert(&display->deferred_list, &task->link); +} + +void +display_watch_fd(struct display *display, + int fd, uint32_t events, struct task *task) +{ + struct epoll_event ep; + + ep.events = events; + ep.data.ptr = task; + epoll_ctl(display->epoll_fd, EPOLL_CTL_ADD, fd, &ep); +} + +void +display_unwatch_fd(struct display *display, int fd) +{ + epoll_ctl(display->epoll_fd, EPOLL_CTL_DEL, fd, NULL); +} + +void +display_run(struct display *display) +{ + struct task *task; + struct epoll_event ep[16]; + int i, count, ret; + + display->running = 1; + while (1) { + while (!wl_list_empty(&display->deferred_list)) { + task = container_of(display->deferred_list.prev, + struct task, link); + wl_list_remove(&task->link); + task->run(task, 0); + } + + wl_display_dispatch_pending(display->display); + + if (!display->running) + break; + + ret = wl_display_flush(display->display); + if (ret < 0 && errno == EAGAIN) { + ep[0].events = + EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP; + ep[0].data.ptr = &display->display_task; + + epoll_ctl(display->epoll_fd, EPOLL_CTL_MOD, + display->display_fd, &ep[0]); + } else if (ret < 0) { + break; + } + + count = epoll_wait(display->epoll_fd, + ep, ARRAY_LENGTH(ep), -1); + for (i = 0; i < count; i++) { + task = ep[i].data.ptr; + task->run(task, ep[i].events); + } + } +} + +void +display_exit(struct display *display) +{ + display->running = 0; +} + +void +keysym_modifiers_add(struct wl_array *modifiers_map, + const char *name) +{ + size_t len = strlen(name) + 1; + char *p; + + p = wl_array_add(modifiers_map, len); + + if (p == NULL) + return; + + strncpy(p, name, len); +} + +static xkb_mod_index_t +keysym_modifiers_get_index(struct wl_array *modifiers_map, + const char *name) +{ + xkb_mod_index_t index = 0; + char *p = modifiers_map->data; + + while ((const char *)p < (const char *)(modifiers_map->data + modifiers_map->size)) { + if (strcmp(p, name) == 0) + return index; + + index++; + p += strlen(p) + 1; + } + + return XKB_MOD_INVALID; +} + +xkb_mod_mask_t +keysym_modifiers_get_mask(struct wl_array *modifiers_map, + const char *name) +{ + xkb_mod_index_t index = keysym_modifiers_get_index(modifiers_map, name); + + if (index == XKB_MOD_INVALID) + return XKB_MOD_INVALID; + + return 1 << index; +} + +void * +fail_on_null(void *p) +{ + if (p == NULL) { + fprintf(stderr, "%s: out of memory\n", program_invocation_short_name); + exit(EXIT_FAILURE); + } + + return p; +} + +void * +xmalloc(size_t s) +{ + return fail_on_null(malloc(s)); +} + +void * +xzalloc(size_t s) +{ + return fail_on_null(zalloc(s)); +} + +char * +xstrdup(const char *s) +{ + return fail_on_null(strdup(s)); +} diff --git a/clients/window.h b/clients/window.h new file mode 100644 index 00000000..4427ab5a --- /dev/null +++ b/clients/window.h @@ -0,0 +1,599 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef _WINDOW_H_ +#define _WINDOW_H_ + +#include +#include +#include +#include "../shared/config-parser.h" +#include "../shared/zalloc.h" +#include "subsurface-client-protocol.h" + +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) + +#define container_of(ptr, type, member) ({ \ + const __typeof__( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +struct window; +struct widget; +struct display; +struct input; +struct output; + +struct task { + void (*run)(struct task *task, uint32_t events); + struct wl_list link; +}; + +struct rectangle { + int32_t x; + int32_t y; + int32_t width; + int32_t height; +}; + +void * +fail_on_null(void *p); +void * +xmalloc(size_t s); +void * +xzalloc(size_t s); +char * +xstrdup(const char *s); + +struct display * +display_create(int *argc, char *argv[]); + +void +display_destroy(struct display *display); + +void +display_set_user_data(struct display *display, void *data); + +void * +display_get_user_data(struct display *display); + +struct wl_display * +display_get_display(struct display *display); + +int +display_has_subcompositor(struct display *display); + +cairo_device_t * +display_get_cairo_device(struct display *display); + +struct wl_compositor * +display_get_compositor(struct display *display); + +struct wl_shell * +display_get_shell(struct display *display); + +struct output * +display_get_output(struct display *display); + +uint32_t +display_get_serial(struct display *display); + +typedef void (*display_global_handler_t)(struct display *display, + uint32_t name, + const char *interface, + uint32_t version, void *data); + +void +display_set_global_handler(struct display *display, + display_global_handler_t handler); +void * +display_bind(struct display *display, uint32_t name, + const struct wl_interface *interface, uint32_t version); + +typedef void (*display_output_handler_t)(struct output *output, void *data); + +/* + * The output configure handler is called, when a new output is connected + * and we know its current mode, or when the current mode changes. + * Test and set the output user data in your handler to know, if the + * output is new. Note: 'data' in the configure handler is the display + * user data. + */ +void +display_set_output_configure_handler(struct display *display, + display_output_handler_t handler); + +struct wl_data_source * +display_create_data_source(struct display *display); + +#ifdef EGL_NO_DISPLAY +EGLDisplay +display_get_egl_display(struct display *d); + +EGLConfig +display_get_argb_egl_config(struct display *d); + +int +display_acquire_window_surface(struct display *display, + struct window *window, + EGLContext ctx); +void +display_release_window_surface(struct display *display, + struct window *window); +#endif + +#define SURFACE_OPAQUE 0x01 +#define SURFACE_SHM 0x02 + +#define SURFACE_HINT_RESIZE 0x10 + +#define SURFACE_HINT_RGB565 0x100 + +cairo_surface_t * +display_create_surface(struct display *display, + struct wl_surface *surface, + struct rectangle *rectangle, + uint32_t flags); + +struct wl_buffer * +display_get_buffer_for_surface(struct display *display, + cairo_surface_t *surface); + +struct wl_cursor_image * +display_get_pointer_image(struct display *display, int pointer); + +void +display_defer(struct display *display, struct task *task); + +void +display_watch_fd(struct display *display, + int fd, uint32_t events, struct task *task); + +void +display_unwatch_fd(struct display *display, int fd); + +void +display_run(struct display *d); + +void +display_exit(struct display *d); + +enum cursor_type { + CURSOR_BOTTOM_LEFT, + CURSOR_BOTTOM_RIGHT, + CURSOR_BOTTOM, + CURSOR_DRAGGING, + CURSOR_LEFT_PTR, + CURSOR_LEFT, + CURSOR_RIGHT, + CURSOR_TOP_LEFT, + CURSOR_TOP_RIGHT, + CURSOR_TOP, + CURSOR_IBEAM, + CURSOR_HAND1, + CURSOR_WATCH, + + CURSOR_BLANK +}; + +typedef void (*window_key_handler_t)(struct window *window, struct input *input, + uint32_t time, uint32_t key, uint32_t unicode, + enum wl_keyboard_key_state state, void *data); + +typedef void (*window_keyboard_focus_handler_t)(struct window *window, + struct input *device, void *data); + +typedef void (*window_data_handler_t)(struct window *window, + struct input *input, + float x, float y, + const char **types, + void *data); + +typedef void (*window_drop_handler_t)(struct window *window, + struct input *input, + int32_t x, int32_t y, void *data); + +typedef void (*window_close_handler_t)(struct window *window, void *data); +typedef void (*window_fullscreen_handler_t)(struct window *window, void *data); + +typedef void (*window_output_handler_t)(struct window *window, struct output *output, + int enter, void *data); + +typedef void (*widget_resize_handler_t)(struct widget *widget, + int32_t width, int32_t height, + void *data); +typedef void (*widget_redraw_handler_t)(struct widget *widget, void *data); + +typedef int (*widget_enter_handler_t)(struct widget *widget, + struct input *input, + float x, float y, void *data); +typedef void (*widget_leave_handler_t)(struct widget *widget, + struct input *input, void *data); +typedef int (*widget_motion_handler_t)(struct widget *widget, + struct input *input, uint32_t time, + float x, float y, void *data); +typedef void (*widget_button_handler_t)(struct widget *widget, + struct input *input, uint32_t time, + uint32_t button, + enum wl_pointer_button_state state, + void *data); +typedef void (*widget_touch_down_handler_t)(struct widget *widget, + struct input *input, + uint32_t serial, + uint32_t time, + int32_t id, + float x, + float y, + void *data); +typedef void (*widget_touch_up_handler_t)(struct widget *widget, + struct input *input, + uint32_t serial, + uint32_t time, + int32_t id, + void *data); +typedef void (*widget_touch_motion_handler_t)(struct widget *widget, + struct input *input, + uint32_t time, + int32_t id, + float x, + float y, + void *data); +typedef void (*widget_touch_frame_handler_t)(struct widget *widget, + struct input *input, void *data); +typedef void (*widget_touch_cancel_handler_t)(struct widget *widget, + struct input *input, void *data); +typedef void (*widget_axis_handler_t)(struct widget *widget, + struct input *input, uint32_t time, + uint32_t axis, + wl_fixed_t value, + void *data); + +struct window * +window_create(struct display *display); +struct window * +window_create_transient(struct display *display, struct window *parent, + int32_t x, int32_t y, uint32_t flags); +struct window * +window_create_custom(struct display *display); + +int +window_has_focus(struct window *window); + +typedef void (*menu_func_t)(struct window *window, int index, void *data); + +void +window_show_menu(struct display *display, + struct input *input, uint32_t time, struct window *parent, + int32_t x, int32_t y, + menu_func_t func, const char **entries, int count); + +void +window_show_frame_menu(struct window *window, + struct input *input, uint32_t time); + +int +window_get_buffer_transform(struct window *window); + +void +window_set_buffer_transform(struct window *window, + enum wl_output_transform transform); + +uint32_t +window_get_buffer_scale(struct window *window); + +void +window_set_buffer_scale(struct window *window, + int32_t scale); + +uint32_t +window_get_output_scale(struct window *window); + +void +window_destroy(struct window *window); + +struct widget * +window_add_widget(struct window *window, void *data); + +enum subsurface_mode { + SUBSURFACE_SYNCHRONIZED, + SUBSURFACE_DESYNCHRONIZED +}; + +struct widget * +window_add_subsurface(struct window *window, void *data, + enum subsurface_mode default_mode); + +typedef void (*data_func_t)(void *data, size_t len, + int32_t x, int32_t y, void *user_data); + +struct display * +window_get_display(struct window *window); +void +window_move(struct window *window, struct input *input, uint32_t time); +void +window_touch_move(struct window *window, struct input *input, uint32_t time); +void +window_get_allocation(struct window *window, struct rectangle *allocation); +void +window_schedule_redraw(struct window *window); +void +window_schedule_resize(struct window *window, int width, int height); + +void +window_damage(struct window *window, int32_t x, int32_t y, + int32_t width, int32_t height); + +cairo_surface_t * +window_get_surface(struct window *window); + +struct wl_surface * +window_get_wl_surface(struct window *window); + +struct wl_shell_surface * +window_get_wl_shell_surface(struct window *window); + +enum window_buffer_type { + WINDOW_BUFFER_TYPE_EGL_WINDOW, + WINDOW_BUFFER_TYPE_SHM, +}; + +void +display_surface_damage(struct display *display, cairo_surface_t *cairo_surface, + int32_t x, int32_t y, int32_t width, int32_t height); + +void +window_set_buffer_type(struct window *window, enum window_buffer_type type); + +int +window_is_fullscreen(struct window *window); + +void +window_set_fullscreen(struct window *window, int fullscreen); + +void +window_set_fullscreen_method(struct window *window, + enum wl_shell_surface_fullscreen_method method); +int +window_is_maximized(struct window *window); + +void +window_set_maximized(struct window *window, int maximized); + +void +window_set_user_data(struct window *window, void *data); + +void * +window_get_user_data(struct window *window); + +void +window_set_key_handler(struct window *window, + window_key_handler_t handler); + +void +window_set_keyboard_focus_handler(struct window *window, + window_keyboard_focus_handler_t handler); + +void +window_set_data_handler(struct window *window, + window_data_handler_t handler); + +void +window_set_drop_handler(struct window *window, + window_drop_handler_t handler); + +void +window_set_close_handler(struct window *window, + window_close_handler_t handler); +void +window_set_fullscreen_handler(struct window *window, + window_fullscreen_handler_t handler); +void +window_set_output_handler(struct window *window, + window_output_handler_t handler); + +void +window_set_title(struct window *window, const char *title); + +const char * +window_get_title(struct window *window); + +void +window_set_text_cursor_position(struct window *window, int32_t x, int32_t y); + +enum preferred_format { + WINDOW_PREFERRED_FORMAT_NONE, + WINDOW_PREFERRED_FORMAT_RGB565 +}; + +void +window_set_preferred_format(struct window *window, + enum preferred_format format); + +int +widget_set_tooltip(struct widget *parent, char *entry, float x, float y); + +void +widget_destroy_tooltip(struct widget *parent); + +struct widget * +widget_add_widget(struct widget *parent, void *data); + +void +widget_destroy(struct widget *widget); +void +widget_set_default_cursor(struct widget *widget, int cursor); +void +widget_get_allocation(struct widget *widget, struct rectangle *allocation); + +void +widget_set_allocation(struct widget *widget, + int32_t x, int32_t y, int32_t width, int32_t height); +void +widget_set_size(struct widget *widget, int32_t width, int32_t height); +void +widget_set_transparent(struct widget *widget, int transparent); +void +widget_schedule_resize(struct widget *widget, int32_t width, int32_t height); + +void * +widget_get_user_data(struct widget *widget); + +cairo_t * +widget_cairo_create(struct widget *widget); + +struct wl_surface * +widget_get_wl_surface(struct widget *widget); + +uint32_t +widget_get_last_time(struct widget *widget); + +void +widget_input_region_add(struct widget *widget, const struct rectangle *rect); + +void +widget_set_redraw_handler(struct widget *widget, + widget_redraw_handler_t handler); +void +widget_set_resize_handler(struct widget *widget, + widget_resize_handler_t handler); +void +widget_set_enter_handler(struct widget *widget, + widget_enter_handler_t handler); +void +widget_set_leave_handler(struct widget *widget, + widget_leave_handler_t handler); +void +widget_set_motion_handler(struct widget *widget, + widget_motion_handler_t handler); +void +widget_set_button_handler(struct widget *widget, + widget_button_handler_t handler); +void +widget_set_touch_down_handler(struct widget *widget, + widget_touch_down_handler_t handler); +void +widget_set_touch_up_handler(struct widget *widget, + widget_touch_up_handler_t handler); +void +widget_set_touch_motion_handler(struct widget *widget, + widget_touch_motion_handler_t handler); +void +widget_set_touch_frame_handler(struct widget *widget, + widget_touch_frame_handler_t handler); +void +widget_set_touch_cancel_handler(struct widget *widget, + widget_touch_cancel_handler_t handler); +void +widget_set_axis_handler(struct widget *widget, + widget_axis_handler_t handler); +void +widget_schedule_redraw(struct widget *widget); + +struct widget * +frame_create(struct window *window, void *data); + +void +frame_set_child_size(struct widget *widget, int child_width, int child_height); + +void +input_set_pointer_image(struct input *input, int pointer); + +void +input_get_position(struct input *input, int32_t *x, int32_t *y); + +#define MOD_SHIFT_MASK 0x01 +#define MOD_ALT_MASK 0x02 +#define MOD_CONTROL_MASK 0x04 + +uint32_t +input_get_modifiers(struct input *input); + +void +input_grab(struct input *input, struct widget *widget, uint32_t button); + +void +input_ungrab(struct input *input); + +struct widget * +input_get_focus_widget(struct input *input); + +struct display * +input_get_display(struct input *input); + +struct wl_seat * +input_get_seat(struct input *input); + +struct wl_data_device * +input_get_data_device(struct input *input); + +void +input_set_selection(struct input *input, + struct wl_data_source *source, uint32_t time); + +void +input_accept(struct input *input, const char *type); + + +void +input_receive_drag_data(struct input *input, const char *mime_type, + data_func_t func, void *user_data); +int +input_receive_drag_data_to_fd(struct input *input, + const char *mime_type, int fd); + +int +input_receive_selection_data(struct input *input, const char *mime_type, + data_func_t func, void *data); +int +input_receive_selection_data_to_fd(struct input *input, + const char *mime_type, int fd); + +void +output_set_user_data(struct output *output, void *data); + +void * +output_get_user_data(struct output *output); + +void +output_set_destroy_handler(struct output *output, + display_output_handler_t handler); + +void +output_get_allocation(struct output *output, struct rectangle *allocation); + +struct wl_output * +output_get_wl_output(struct output *output); + +enum wl_output_transform +output_get_transform(struct output *output); + +uint32_t +output_get_scale(struct output *output); + +void +keysym_modifiers_add(struct wl_array *modifiers_map, + const char *name); + +xkb_mod_mask_t +keysym_modifiers_get_mask(struct wl_array *modifiers_map, + const char *name); + +#endif diff --git a/clients/wscreensaver-glue.c b/clients/wscreensaver-glue.c new file mode 100644 index 00000000..55d0a8c7 --- /dev/null +++ b/clients/wscreensaver-glue.c @@ -0,0 +1,148 @@ +/* + * Copyright © 2011 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "wscreensaver-glue.h" + +double frand(double f) +{ + double r = random(); + return r * f / (double)RAND_MAX; +} + +void clear_gl_error(void) +{ + while (glGetError() != GL_NO_ERROR) + ; +} + +void check_gl_error(const char *msg) +{ + const char *emsg; + int err = glGetError(); + + switch (err) + { + case GL_NO_ERROR: + return; + + #define ERR(tok) case tok: emsg = #tok; break; + ERR(GL_INVALID_ENUM) + ERR(GL_INVALID_VALUE) + ERR(GL_INVALID_OPERATION) + ERR(GL_STACK_OVERFLOW) + ERR(GL_STACK_UNDERFLOW) + ERR(GL_OUT_OF_MEMORY) + #undef ERR + + default: + fprintf(stderr, "%s: %s: unknown GL error 0x%04x\n", + progname, msg, err); + exit(1); + } + + fprintf(stderr, "%s: %s: GL error %s\n", progname, msg, emsg); + exit(1); +} + +static void +read_xpm_color(uint32_t *ctable, const char *line) +{ + unsigned char key; + char cstr[10]; + char *end; + uint32_t value; + + if (sscanf(line, "%1c c %9s", &key, cstr) < 2) { + fprintf(stderr, "%s: error in XPM color definition '%s'\n", + progname, line); + return; + } + + value = strtol(&cstr[1], &end, 16); + + if (strcmp(cstr, "None") == 0) + ctable[key] = 0x00000000; + else if (cstr[0] != '#' || !(cstr[1] != '\0' && *end == '\0')) { + fprintf(stderr, "%s: error interpreting XPM color '%s'\n", + progname, cstr); + return; + } else { + ctable[key] = value | 0xff000000; + } +} + +static void +read_xpm_row(char *data, const char *line, uint32_t *ctable, int width) +{ + uint32_t *pixel = (uint32_t *)data; + uint8_t *p = (uint8_t *)line; + int i; + + for (i = 0; i < width; ++i) + pixel[i] = ctable[p[i]]; +} + +XImage *xpm_to_ximage(char **xpm_data) +{ + XImage *xi; + int colors; + int cpp; + int i; + uint32_t ctable[256] = { 0 }; + + xi = malloc(sizeof *xi); + if (!xi) + return NULL; + xi->data = NULL; + + if (sscanf(xpm_data[0], "%d %d %d %d", &xi->width, + &xi->height, &colors, &cpp) < 4) + goto errout; + + if (xi->width < 1 || xi->height < 1 || cpp != 1) + goto errout; + + xi->bytes_per_line = xi->width * sizeof(uint32_t); + xi->data = malloc(xi->height * xi->bytes_per_line); + if (!xi->data) + goto errout; + + for (i = 0; i < colors; ++i) + read_xpm_color(ctable, xpm_data[i + 1]); + + for (i = 0; i < xi->height; ++i) + read_xpm_row(xi->data + i * xi->bytes_per_line, + xpm_data[i + colors + 1], ctable, xi->width); + + return xi; + +errout: + fprintf(stderr, "%s: error processing XPM data.\n", progname); + XDestroyImage(xi); + return NULL; +} + +void XDestroyImage(XImage *xi) +{ + free(xi->data); + free(xi); +} diff --git a/clients/wscreensaver-glue.h b/clients/wscreensaver-glue.h new file mode 100644 index 00000000..e07915f9 --- /dev/null +++ b/clients/wscreensaver-glue.h @@ -0,0 +1,120 @@ +/* + * Copyright © 2011 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef WSCREENSAVER_GLUE_H +#define WSCREENSAVER_GLUE_H + +/* + * This file is glue, that tries to avoid changing glmatrix.c from the + * original too much, hopefully easing the porting of other (GL) + * xscreensaver "hacks". + */ + +#include "wscreensaver.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "window.h" + +#define ENTRYPOINT static + +typedef bool Bool; +#define True true +#define False false + +typedef struct ModeInfo ModeInfo; + +#define MI_DISPLAY(mi) NULL +#define MI_WINDOW(mi) (mi) +#define MI_SCREEN(mi) ((mi)->instance_number) +#define MI_WIDTH(mi) ((mi)->width) +#define MI_HEIGHT(mi) ((mi)->height) +#define MI_IS_WIREFRAME(mi) 0 +#define MI_NUM_SCREENS(mi) 16 + +typedef EGLContext GLXContext; + +double frand(double f); +void clear_gl_error(void); +void check_gl_error(const char *msg); + +static inline void +glXMakeCurrent(void *dummy, ModeInfo *mi, EGLContext ctx) +{ + assert(mi->eglctx == ctx); +} + +static inline void +glXSwapBuffers(void *dummy, ModeInfo *mi) +{ + mi->swap_buffers = 1; +} + +static inline void +do_fps(ModeInfo *mi) +{ +} + +/* just enough XImage to satisfy glmatrix.c */ + +typedef struct _XImage { + int width; + int height; + char *data; + int bytes_per_line; +} XImage; + +XImage *xpm_to_ximage(char **xpm_data); +void XDestroyImage(XImage *xi); + +static inline unsigned long +XGetPixel(XImage *xi, int x, int y) +{ + return *(uint32_t *)(xi->data + xi->bytes_per_line * y + 4 * x); +} + +static inline void +XPutPixel(XImage *xi, int x, int y, unsigned long pixel) +{ + *(uint32_t *)(xi->data + xi->bytes_per_line * y + 4 * x) = pixel; +} + +/* + * override glViewport from the plugin, so we can set it up properly + * rendering to a regular decorated Wayland window. + */ +#ifdef glViewport +#undef glViewport +#endif +#define glViewport(x,y,w,h) do {} while (0) + +#endif /* WSCREENSAVER_GLUE_H */ diff --git a/clients/wscreensaver.c b/clients/wscreensaver.c new file mode 100644 index 00000000..9a2c47ad --- /dev/null +++ b/clients/wscreensaver.c @@ -0,0 +1,344 @@ +/* + * Copyright © 2011 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "../config.h" + +#include "wscreensaver.h" + +#include +#include +#include +#include + +#include +#include + +#include + +#include "desktop-shell-client-protocol.h" +#include "window.h" + +extern struct wscreensaver_plugin glmatrix_screensaver; + +static const struct wscreensaver_plugin * const plugins[] = { + &glmatrix_screensaver, + NULL +}; + +const char *progname = NULL; + +static int demo_mode; + +struct wscreensaver { + struct screensaver *interface; + + struct display *display; + + struct ModeInfo *demomode; + + struct { + EGLDisplay display; + EGLConfig config; + } egl; + + const struct wscreensaver_plugin *plugin; +}; + +static void +frame_callback(void *data, struct wl_callback *callback, uint32_t time) +{ + struct ModeInfo *mi = data; + + window_schedule_redraw(mi->window); + wl_callback_destroy(callback); +} + +static const struct wl_callback_listener listener = { + frame_callback +}; + +static void +redraw_handler(struct widget *widget, void *data) +{ + struct ModeInfo *mi = data; + struct wscreensaver *wscr = mi->priv; + struct rectangle drawarea; + struct rectangle winarea; + struct wl_callback *callback; + int bottom; + + mi->swap_buffers = 0; + + widget_get_allocation(mi->widget, &drawarea); + window_get_allocation(mi->window, &winarea); + + if (display_acquire_window_surface(wscr->display, + mi->window, + mi->eglctx) < 0) { + fprintf(stderr, "%s: unable to acquire window surface", + progname); + return; + } + + bottom = winarea.height - (drawarea.height + drawarea.y); + glViewport(drawarea.x, bottom, drawarea.width, drawarea.height); + glScissor(drawarea.x, bottom, drawarea.width, drawarea.height); + glEnable(GL_SCISSOR_TEST); + + if (mi->width != drawarea.width || mi->height != drawarea.height) { + mi->width = drawarea.width; + mi->height = drawarea.height; + wscr->plugin->reshape(mi, mi->width, mi->height); + } + + wscr->plugin->draw(mi); + + if (mi->swap_buffers == 0) + fprintf(stderr, "%s: swapBuffers not called\n", progname); + + display_release_window_surface(wscr->display, mi->window); + + callback = wl_surface_frame(window_get_wl_surface(mi->window)); + wl_callback_add_listener(callback, &listener, mi); +} + +static void +init_frand(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + srandom(tv.tv_sec * 100 + tv.tv_usec / 10000); +} + +WL_EXPORT EGLContext * +init_GL(struct ModeInfo *mi) +{ + struct wscreensaver *wscr = mi->priv; + EGLContext *pctx; + + pctx = malloc(sizeof *pctx); + if (!pctx) + return NULL; + + if (mi->eglctx != EGL_NO_CONTEXT) { + fprintf(stderr, "%s: multiple GL contexts are not supported", + progname); + goto errout; + } + + mi->eglctx = eglCreateContext(wscr->egl.display, wscr->egl.config, + EGL_NO_CONTEXT, NULL); + if (mi->eglctx == EGL_NO_CONTEXT) { + fprintf(stderr, "%s: init_GL failed to create EGL context\n", + progname); + goto errout; + } + + if (!eglMakeCurrent(wscr->egl.display, NULL, NULL, mi->eglctx)) { + fprintf(stderr, "%s: init_GL failed on eglMakeCurrent\n", + progname); + goto errout; + } + + glClearColor(0.0, 0.0, 0.0, 1.0); + + *pctx = mi->eglctx; + return pctx; + +errout: + free(pctx); + return NULL; +} + +static struct ModeInfo * +create_wscreensaver_instance(struct wscreensaver *screensaver, + struct wl_output *output, int width, int height) +{ + static int instance; + struct ModeInfo *mi; + struct rectangle drawarea; + + mi = calloc(1, sizeof *mi); + if (!mi) + return NULL; + + if (demo_mode) + mi->window = window_create(screensaver->display); + else + mi->window = window_create_custom(screensaver->display); + + if (!mi->window) { + fprintf(stderr, "%s: creating a window failed.\n", progname); + free(mi); + return NULL; + } + + window_set_title(mi->window, progname); + + if (screensaver->interface && !demo_mode) { + mi->widget = window_add_widget(mi->window, mi); + screensaver_set_surface(screensaver->interface, + window_get_wl_surface(mi->window), + output); + } else { + mi->widget = frame_create(mi->window, mi); + } + widget_set_redraw_handler(mi->widget, redraw_handler); + + mi->priv = screensaver; + mi->eglctx = EGL_NO_CONTEXT; + mi->instance_number = instance++; /* XXX */ + + widget_get_allocation(mi->widget, &drawarea); + mi->width = drawarea.width; + mi->height = drawarea.height; + + screensaver->plugin->init(mi); + + window_schedule_resize(mi->window, width, height); + return mi; +} + +static void +handle_output_destroy(struct output *output, void *data) +{ + /* struct ModeInfo *mi = data; + * TODO */ +} + +static void +handle_output_configure(struct output *output, void *data) +{ + struct wscreensaver *screensaver = data; + struct ModeInfo *mi; + struct rectangle area; + + /* skip existing outputs */ + if (output_get_user_data(output)) + return; + + output_get_allocation(output, &area); + mi = create_wscreensaver_instance(screensaver, + output_get_wl_output(output), + area.width, area.height); + output_set_user_data(output, mi); + output_set_destroy_handler(output, handle_output_destroy); +} + +static int +init_wscreensaver(struct wscreensaver *wscr, struct display *display) +{ + int size; + const char prefix[] = "wscreensaver::"; + char *str; + + display_set_user_data(display, wscr); + wscr->display = display; + wscr->plugin = plugins[0]; + + size = sizeof(prefix) + strlen(wscr->plugin->name); + str = malloc(size); + if (!str) { + fprintf(stderr, "init: out of memory\n"); + return -1; + } + snprintf(str, size, "%s%s", prefix, wscr->plugin->name); + progname = str; + + wscr->egl.display = display_get_egl_display(wscr->display); + if (!wscr->egl.display) { + fprintf(stderr, "init: no EGL display\n"); + return -1; + } + + eglBindAPI(EGL_OPENGL_API); + wscr->egl.config = display_get_argb_egl_config(wscr->display); + + if (demo_mode) { + struct wl_output *o = + output_get_wl_output(display_get_output(display)); + /* only one instance */ + wscr->demomode = + create_wscreensaver_instance(wscr, o, 400, 300); + return 0; + } + + display_set_output_configure_handler(display, handle_output_configure); + + return 0; +} + +static void +global_handler(struct display *display, uint32_t name, + const char *interface, uint32_t version, void *data) +{ + struct wscreensaver *screensaver = data; + + if (!strcmp(interface, "screensaver")) { + screensaver->interface = + display_bind(display, name, &screensaver_interface, 1); + } +} + +static const struct weston_option wscreensaver_options[] = { + { WESTON_OPTION_BOOLEAN, "demo", 0, &demo_mode }, +}; + +int main(int argc, char *argv[]) +{ + struct display *d; + struct wscreensaver screensaver = { 0 }; + + init_frand(); + + parse_options(wscreensaver_options, + ARRAY_LENGTH(wscreensaver_options), &argc, argv); + + d = display_create(&argc, argv); + if (d == NULL) { + fprintf(stderr, "failed to create display: %m\n"); + return EXIT_FAILURE; + } + + if (!demo_mode) { + /* iterates already known globals immediately */ + display_set_user_data(d, &screensaver); + display_set_global_handler(d, global_handler); + if (!screensaver.interface) { + fprintf(stderr, + "Server did not offer screensaver interface," + " exiting.\n"); + return EXIT_FAILURE; + } + } + + if (init_wscreensaver(&screensaver, d) < 0) { + fprintf(stderr, "wscreensaver init failed.\n"); + return EXIT_FAILURE; + } + + display_run(d); + + free((void *)progname); + + return EXIT_SUCCESS; +} diff --git a/clients/wscreensaver.h b/clients/wscreensaver.h new file mode 100644 index 00000000..e2749d97 --- /dev/null +++ b/clients/wscreensaver.h @@ -0,0 +1,61 @@ +/* + * Copyright © 2011 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef WSCREENSAVER_H +#define WSCREENSAVER_H + +#define MESA_EGL_NO_X11_HEADERS +#include + +extern const char *progname; + +struct wscreensaver; + +struct ModeInfo { + struct wscreensaver *priv; + EGLContext eglctx; + int swap_buffers; + + struct window *window; + struct widget *widget; + + int instance_number; + int width; + int height; + + unsigned long polygon_count; + int fps_p; +}; + +struct wscreensaver_plugin { + const char *name; + void (*init)(struct ModeInfo *mi); + void (*draw)(struct ModeInfo *mi); + void (*reshape)(struct ModeInfo *mi, int w, int h); +/* void (*refresh)(struct ModeInfo *mi); + void (*finish)(struct ModeInfo *mi);*/ +}; + +EGLContext * +init_GL(struct ModeInfo *mi); + +#endif /* WSCREENSAVER_H */ diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..50bce23a --- /dev/null +++ b/configure.ac @@ -0,0 +1,498 @@ +m4_define([weston_major_version], [1]) +m4_define([weston_minor_version], [3]) +m4_define([weston_micro_version], [1]) +m4_define([weston_version], + [weston_major_version.weston_minor_version.weston_micro_version]) + +AC_PREREQ([2.64]) +AC_INIT([weston], + [weston_version], + [https://bugs.freedesktop.org/enter_bug.cgi?product=Wayland&component=weston&version=weston_version], + [weston], + [http://wayland.freedesktop.org/]) + +AC_SUBST([WESTON_VERSION_MAJOR], [weston_major_version]) +AC_SUBST([WESTON_VERSION_MINOR], [weston_minor_version]) +AC_SUBST([WESTON_VERSION_MICRO], [weston_micro_version]) +AC_SUBST([WESTON_VERSION], [weston_version]) + +AC_CONFIG_HEADERS([config.h]) + +AC_USE_SYSTEM_EXTENSIONS +AC_SYS_LARGEFILE + +AM_INIT_AUTOMAKE([1.11 parallel-tests foreign no-dist-gzip dist-xz color-tests]) + +AM_SILENT_RULES([yes]) + +# Check for programs +AC_PROG_CC +AC_PROG_SED + +# Initialize libtool +LT_PREREQ([2.2]) +LT_INIT([disable-static]) + +AC_ARG_VAR([WESTON_NATIVE_BACKEND], + [Set the native backend to use, if Weston is not running under Wayland nor X11. @<:@default=drm-backend.so@:>@]) + +PKG_PROG_PKG_CONFIG() + +AC_CHECK_FUNC([dlopen], [], + AC_CHECK_LIB([dl], [dlopen], DLOPEN_LIBS="-ldl")) +AC_SUBST(DLOPEN_LIBS) + +AC_CHECK_DECL(SFD_CLOEXEC,[], + [AC_MSG_ERROR("SFD_CLOEXEC is needed to compile weston")], + [[#include ]]) +AC_CHECK_DECL(TFD_CLOEXEC,[], + [AC_MSG_ERROR("TFD_CLOEXEC is needed to compile weston")], + [[#include ]]) +AC_CHECK_DECL(CLOCK_MONOTONIC,[], + [AC_MSG_ERROR("CLOCK_MONOTONIC is needed to compile weston")], + [[#include ]]) +AC_CHECK_HEADERS([execinfo.h]) + +AC_CHECK_FUNCS([mkostemp strchrnul initgroups]) + +COMPOSITOR_MODULES="wayland-server >= 1.2.91 pixman-1" + +AC_ARG_ENABLE(egl, [ --disable-egl],, + enable_egl=yes) +AM_CONDITIONAL(ENABLE_EGL, test x$enable_egl = xyes) +if test x$enable_egl = xyes; then + AC_DEFINE([ENABLE_EGL], [1], [Build Weston with EGL support]) + COMPOSITOR_MODULES="$COMPOSITOR_MODULES egl >= 7.10 glesv2" +fi + +AC_ARG_ENABLE(xkbcommon, + AS_HELP_STRING([--disable-xkbcommon], [Disable libxkbcommon + support: This is only useful in environments + where you do not have a hardware keyboard. If + libxkbcommon support is disabled clients will not + be sent a keymap and and must know how to + interpret the keycode sent for any key event.]),, + enable_xkbcommon=yes) +if test x$enable_xkbcommon = xyes; then + AC_DEFINE(ENABLE_XKBCOMMON, [1], [Build Weston with libxkbcommon support]) + COMPOSITOR_MODULES="$COMPOSITOR_MODULES xkbcommon >= 0.3.0" +fi + +PKG_CHECK_MODULES(COMPOSITOR, [$COMPOSITOR_MODULES]) + +AC_ARG_ENABLE(setuid-install, [ --enable-setuid-install],, + enable_setuid_install=yes) +AM_CONDITIONAL(ENABLE_SETUID_INSTALL, test x$enable_setuid_install = xyes) + + +AC_ARG_ENABLE(xwayland, [ --enable-xwayland],, + enable_xwayland=yes) +AC_ARG_ENABLE(xwayland-test, [ --enable-xwayland-test],, + enable_xwayland_test=yes) +AM_CONDITIONAL(ENABLE_XWAYLAND, test x$enable_xwayland = xyes) +AM_CONDITIONAL(ENABLE_XWAYLAND_TEST, test x$enable_xwayland = xyes -a x$enable_xwayland_test = xyes) +if test x$enable_xwayland = xyes; then + PKG_CHECK_MODULES([XWAYLAND], xcb xcb-xfixes xcb-composite xcursor cairo-xcb) + AC_DEFINE([BUILD_XWAYLAND], [1], [Build the X server launcher]) + + AC_ARG_WITH(xserver-path, AS_HELP_STRING([--with-xserver-path=PATH], + [Path to X server]), [XSERVER_PATH="$withval"], + [XSERVER_PATH="$bindir/Xorg"]) + AC_SUBST([XSERVER_PATH]) + if test x$enable_xwayland_test = xyes; then + PKG_CHECK_MODULES([XWAYLAND_TEST], xcb xcb-dri2 libdrm) + fi +fi + + +AC_ARG_ENABLE(x11-compositor, [ --enable-x11-compositor],, + enable_x11_compositor=yes) +AM_CONDITIONAL(ENABLE_X11_COMPOSITOR, test x$enable_x11_compositor = xyes) +if test x$enable_x11_compositor = xyes; then + PKG_CHECK_MODULES([XCB], xcb) + xcb_save_LIBS=$LIBS + xcb_save_CFLAGS=$CFLAGS + CFLAGS=$XCB_CFLAGS + LIBS=$XCB_LIBS + AC_CHECK_FUNCS([xcb_poll_for_queued_event]) + LIBS=$xcb_save_LIBS + CFLAGS=$xcb_save_CFLAGS + + X11_COMPOSITOR_MODULES="x11 x11-xcb xcb-shm" + + PKG_CHECK_MODULES(X11_COMPOSITOR_XKB, [xcb-xkb], + [have_xcb_xkb="yes"], [have_xcb_xkb="no"]) + if test "x$have_xcb_xkb" = xyes; then + # Most versions of XCB have totally broken XKB bindings, where the + # events don't work. Make sure we can actually use them. + xcb_xkb_save_CFLAGS=$CFLAGS + CFLAGS=$X11_COMPOSITOR_XKB_CFLAGS + AC_CHECK_MEMBER([struct xcb_xkb_state_notify_event_t.xkbType], + [], [have_xcb_xkb=no], [[#include ]]) + CFLAGS=$xcb_xkb_save_CFLAGS + fi + if test "x$have_xcb_xkb" = xyes; then + X11_COMPOSITOR_MODULES="$X11_COMPOSITOR_MODULES xcb-xkb" + AC_DEFINE([HAVE_XCB_XKB], [1], [libxcb supports XKB protocol]) + fi + + PKG_CHECK_MODULES(X11_COMPOSITOR, [$X11_COMPOSITOR_MODULES]) + AC_DEFINE([BUILD_X11_COMPOSITOR], [1], [Build the X11 compositor]) +fi + + +AC_ARG_ENABLE(drm-compositor, [ --enable-drm-compositor],, + enable_drm_compositor=yes) +AM_CONDITIONAL(ENABLE_DRM_COMPOSITOR, test x$enable_drm_compositor = xyes -a x$enable_egl = xyes) +if test x$enable_drm_compositor = xyes -a x$enable_egl = xyes; then + AC_DEFINE([BUILD_DRM_COMPOSITOR], [1], [Build the DRM compositor]) + PKG_CHECK_MODULES(DRM_COMPOSITOR, [libudev >= 136 libdrm >= 2.4.30 gbm mtdev >= 1.1.0]) +fi + + +AC_ARG_ENABLE(wayland-compositor, [ --enable-wayland-compositor],, + enable_wayland_compositor=yes) +AM_CONDITIONAL(ENABLE_WAYLAND_COMPOSITOR, + test x$enable_wayland_compositor = xyes -a x$enable_egl = xyes) +if test x$enable_wayland_compositor = xyes -a x$enable_egl = xyes; then + AC_DEFINE([BUILD_WAYLAND_COMPOSITOR], [1], + [Build the Wayland (nested) compositor]) + PKG_CHECK_MODULES(WAYLAND_COMPOSITOR, [wayland-client wayland-egl]) +fi + + +AC_ARG_ENABLE(headless-compositor, [ --enable-headless-compositor],, + enable_headless_compositor=yes) +AM_CONDITIONAL(ENABLE_HEADLESS_COMPOSITOR, + test x$enable_headless_compositor = xyes) + + +AC_ARG_ENABLE(rpi-compositor, + AS_HELP_STRING([--disable-rpi-compositor], + [do not build the Raspberry Pi backend]),, + enable_rpi_compositor=yes) +AM_CONDITIONAL(ENABLE_RPI_COMPOSITOR, test "x$enable_rpi_compositor" = "xyes") +have_bcm_host="no" +if test "x$enable_rpi_compositor" = "xyes"; then + AC_DEFINE([BUILD_RPI_COMPOSITOR], [1], [Build the compositor for Raspberry Pi]) + PKG_CHECK_MODULES(RPI_COMPOSITOR, [libudev >= 136 mtdev >= 1.1.0]) + PKG_CHECK_MODULES(RPI_BCM_HOST, [bcm_host], + [have_bcm_host="yes" + AC_DEFINE([HAVE_BCM_HOST], [1], [have Raspberry Pi BCM headers])], + [AC_MSG_WARN([Raspberry Pi BCM host libraries not found, will use stubs instead.])]) +fi +AM_CONDITIONAL(INSTALL_RPI_COMPOSITOR, test "x$have_bcm_host" = "xyes") + + +AC_ARG_ENABLE([fbdev-compositor], [ --enable-fbdev-compositor],, + enable_fbdev_compositor=yes) +AM_CONDITIONAL([ENABLE_FBDEV_COMPOSITOR], + [test x$enable_fbdev_compositor = xyes]) +AS_IF([test x$enable_fbdev_compositor = xyes], [ + AC_DEFINE([BUILD_FBDEV_COMPOSITOR], [1], [Build the fbdev compositor]) + PKG_CHECK_MODULES([FBDEV_COMPOSITOR], [libudev >= 136 mtdev >= 1.1.0]) +]) + +AC_ARG_ENABLE([rdp-compositor], [ --enable-rdp-compositor],, + enable_rdp_compositor=no) +AM_CONDITIONAL([ENABLE_RDP_COMPOSITOR], + [test x$enable_rdp_compositor = xyes]) +if test x$enable_rdp_compositor = xyes; then + AC_DEFINE([BUILD_RDP_COMPOSITOR], [1], [Build the RDP compositor]) + PKG_CHECK_MODULES(RDP_COMPOSITOR, [freerdp >= 1.1.0]) +fi + +AC_ARG_WITH(cairo, + AS_HELP_STRING([--with-cairo=@<:@image|gl|glesv2@:>@] + [Which Cairo renderer to use for the clients]), + [],[with_cairo="image"]) + +if test "x$with_cairo" = "ximage"; then + cairo_modules="cairo" +else +if test "x$with_cairo" = "xgl"; then + cairo_modules="cairo-gl" +else +if test "x$with_cairo" = "xglesv2"; then + cairo_modules="cairo-glesv2" +else + AC_ERROR([Unknown cairo renderer requested]) +fi +fi +fi + +# Included for legacy compat +AC_ARG_WITH(cairo-glesv2, + AS_HELP_STRING([--with-cairo-glesv2], + [Use GLESv2 cairo])) +if test "x$with_cairo_glesv2" = "xyes"; then + cairo_modules="cairo-glesv2" + with_cairo="glesv2" +fi + +if test "x$cairo_modules" = "xcairo-glesv2"; then +AC_DEFINE([USE_CAIRO_GLESV2], [1], [Use the GLESv2 GL cairo backend]) +fi + +PKG_CHECK_MODULES(PIXMAN, [pixman-1]) +PKG_CHECK_MODULES(PNG, [libpng]) +PKG_CHECK_MODULES(WEBP, [libwebp], [have_webp=yes], [have_webp=no]) +AS_IF([test "x$have_webp" = "xyes"], + [AC_DEFINE([HAVE_WEBP], [1], [Have webp])]) + +AC_ARG_ENABLE(vaapi-recorder, [ --enable-vaapi-recorder],, + enable_vaapi_recorder=auto) +if test x$enable_vaapi_recorder != xno; then + PKG_CHECK_MODULES(LIBVA, [libva >= 0.34.0 libva-drm >= 0.34.0], + [have_libva=yes], [have_libva=no]) + if test "x$have_libva" = "xno" -a "x$enable_vaapi_recorder" = "xyes"; then + AC_MSG_ERROR([vaapi-recorder explicitly enabled, but libva couldn't be found]) + fi + AS_IF([test "x$have_libva" = "xyes"], + [AC_DEFINE([BUILD_VAAPI_RECORDER], [1], [Build the vaapi recorder])]) +fi +AM_CONDITIONAL(ENABLE_VAAPI_RECORDER, test "x$have_libva" = xyes) + + +AC_CHECK_LIB([jpeg], [jpeg_CreateDecompress], have_jpeglib=yes) +if test x$have_jpeglib = xyes; then + JPEG_LIBS="-ljpeg" +else + AC_ERROR([libjpeg not found]) +fi +AC_SUBST(JPEG_LIBS) + +PKG_CHECK_MODULES(CAIRO, [cairo]) + +AC_ARG_ENABLE(simple-clients, + AS_HELP_STRING([--disable-simple-clients], + [do not build the simple wl_shm clients]),, + enable_simple_clients=yes) +AM_CONDITIONAL(BUILD_SIMPLE_CLIENTS, test "x$enable_simple_clients" = "xyes") +if test x$enable_simple_clients = xyes; then + PKG_CHECK_MODULES(SIMPLE_CLIENT, [wayland-client]) +fi + +AC_ARG_ENABLE(simple-egl-clients, + AS_HELP_STRING([--disable-simple-egl-clients], + [do not build the simple EGL clients]),, + enable_simple_egl_clients="$enable_egl") +AM_CONDITIONAL(BUILD_SIMPLE_EGL_CLIENTS, test "x$enable_simple_egl_clients" = "xyes") +if test x$enable_simple_egl_clients = xyes; then + PKG_CHECK_MODULES(SIMPLE_EGL_CLIENT, + [egl >= 7.10 glesv2 wayland-client wayland-egl wayland-cursor]) +fi + +AC_ARG_ENABLE(clients, [ --enable-clients],, enable_clients=yes) +AM_CONDITIONAL(BUILD_CLIENTS, test x$enable_clients = xyes) +if test x$enable_clients = xyes; then + AC_DEFINE([BUILD_CLIENTS], [1], [Build the Wayland clients]) + + PKG_CHECK_MODULES(CLIENT, [wayland-client cairo >= 1.10.0 xkbcommon wayland-cursor]) + PKG_CHECK_MODULES(SERVER, [wayland-server]) + PKG_CHECK_MODULES(WESTON_INFO, [wayland-client]) + + PKG_CHECK_MODULES(POPPLER, [poppler-glib glib-2.0 gobject-2.0 gio-2.0 ], + [have_poppler=yes], [have_poppler=no]) + + # Only check for cairo-egl if a GL or GLES renderer requested + AS_IF([test "x$cairo_modules" = "xcairo-gl" -o "x$cairo_modules" = "xcairo-glesv2"], [ + PKG_CHECK_MODULES(CAIRO_EGL, [wayland-egl egl >= 7.10 cairo-egl >= 1.11.3 $cairo_modules], + [have_cairo_egl=yes], [have_cairo_egl=no]) + AS_IF([test "x$have_cairo_egl" = "xyes"], + [AC_DEFINE([HAVE_CAIRO_EGL], [1], [Have cairo-egl])], + [AC_ERROR([cairo-egl not used because $CAIRO_EGL_PKG_ERRORS])])], + [have_cairo_egl=no]) + + PKG_CHECK_MODULES(PANGO, [pangocairo], [have_pango=yes], [have_pango=no]) +fi + +AC_ARG_ENABLE(resize-optimization, + AS_HELP_STRING([--disable-resize-optimization], + [disable resize optimization allocating a big buffer in toytoolkit]),, + enable_resize_optimization=yes) +AS_IF([test "x$enable_resize_optimization" = "xyes"], + [AC_DEFINE([USE_RESIZE_POOL], [1], [Use resize memory pool as a performance optimization])]) + +AC_ARG_ENABLE(weston-launch, [ --enable-weston-launch],, enable_weston_launch=yes) +AM_CONDITIONAL(BUILD_WESTON_LAUNCH, test x$enable_weston_launch == xyes) +if test x$enable_weston_launch == xyes; then + PKG_CHECK_MODULES(WESTON_LAUNCH, [libdrm]) + PKG_CHECK_MODULES(SYSTEMD_LOGIN, [libsystemd-login], + [have_systemd_login=yes], [have_systemd_login=no]) + AS_IF([test "x$have_systemd_login" = "xyes"], + [AC_DEFINE([HAVE_SYSTEMD_LOGIN], [1], [Have systemd-login])]) + + AC_CHECK_LIB([pam], [pam_open_session], [have_pam=yes], [have_pam=no]) + if test x$have_pam == xno; then + AC_ERROR([weston-launch requires pam]) + fi + WESTON_LAUNCH_LIBS="$WESTON_LAUNCH_LIBS -lpam" +fi + +if test x$enable_egl = xyes; then + PKG_CHECK_MODULES(GLU, [glu], [have_glu=yes], [have_glu=no]) +fi +AM_CONDITIONAL(HAVE_GLU, test "x$have_glu" = "xyes") + + +AM_CONDITIONAL(HAVE_POPPLER, test "x$have_poppler" = "xyes") + +AM_CONDITIONAL(HAVE_PANGO, test "x$have_pango" = "xyes") + +AM_CONDITIONAL(HAVE_CAIRO_GLESV2, + [test "x$have_cairo_egl" = "xyes" -a "x$cairo_modules" = "xcairo-glesv2" -a "x$enable_egl" = "xyes"]) + +AM_CONDITIONAL(BUILD_FULL_GL_CLIENTS, + test x$cairo_modules = "xcairo-gl" -a "x$have_cairo_egl" = "xyes" -a "x$enable_egl" = "xyes") + +AM_CONDITIONAL(BUILD_SUBSURFACES_CLIENT, + [test '(' "x$have_cairo_egl" != "xyes" -o "x$cairo_modules" = "xcairo-glesv2" ')' -a "x$enable_simple_egl_clients" = "xyes"]) + +AM_CONDITIONAL(ENABLE_DESKTOP_SHELL, true) + +AC_ARG_ENABLE(tablet-shell, + AS_HELP_STRING([--disable-tablet-shell], + [do not build tablet-shell server plugin and client]),, + enable_tablet_shell=yes) +AM_CONDITIONAL(ENABLE_TABLET_SHELL, + test "x$enable_tablet_shell" = "xyes") + +# CMS modules +AC_ARG_ENABLE(colord, + AS_HELP_STRING([--disable-colord], + [do not build colord CMS support]),, + enable_colord=auto) +if test "x$enable_colord" != "xno"; then + PKG_CHECK_MODULES(COLORD, + colord >= 0.1.27, + have_colord=yes, + have_colord=no) + if test "x$have_colord" = "xno" -a "x$enable_colord" = "xyes"; then + AC_MSG_ERROR([colord support explicitly requested, but colord couldn't be found]) + fi + if test "x$have_colord" = "xyes"; then + enable_colord=yes + fi +fi +AM_CONDITIONAL(ENABLE_COLORD, test "x$enable_colord" = "xyes") + +AC_ARG_ENABLE(wcap-tools, [ --disable-wcap-tools],, enable_wcap_tools=yes) +AM_CONDITIONAL(BUILD_WCAP_TOOLS, test x$enable_wcap_tools = xyes) +if test x$enable_wcap_tools = xyes; then + AC_DEFINE([BUILD_WCAP_TOOLS], [1], [Build the wcap tools]) + PKG_CHECK_MODULES(WCAP, [cairo]) + WCAP_LIBS="$WCAP_LIBS -lm" +fi + +AC_CHECK_PROG(RSVG_CONVERT, rsvg-convert, rsvg-convert) +AM_CONDITIONAL(HAVE_RSVG_CONVERT, test -n "$RSVG_CONVERT") + +PKG_CHECK_MODULES(SETBACKLIGHT, [libudev libdrm], enable_setbacklight=yes, enable_setbacklight=no) +AM_CONDITIONAL(BUILD_SETBACKLIGHT, test "x$enable_setbacklight" = "xyes") + +if test "x$GCC" = "xyes"; then + GCC_CFLAGS="-Wall -Wextra -Wno-unused-parameter \ + -Wno-missing-field-initializers -g -fvisibility=hidden \ + -Wstrict-prototypes -Wmissing-prototypes" +fi +AC_SUBST(GCC_CFLAGS) + +AC_ARG_ENABLE(libunwind, + AS_HELP_STRING([--disable-libunwind], + [Disable libunwind usage for backtraces]),, + enable_libunwind=auto) +AM_CONDITIONAL(HAVE_LIBUNWIND, [test "x$enable_libunwind" = xyes]) +if test "x$enable_libunwind" != "xno"; then + PKG_CHECK_MODULES(LIBUNWIND, + libunwind, + have_libunwind=yes, + have_libunwind=no) + if test "x$have_libunwind" = "xno" -a "x$enable_libunwind" = "xyes"; then + AC_MSG_ERROR([libunwind support explicitly requested, but libunwind couldn't be found]) + fi + if test "x$have_libunwind" = "xyes"; then + enable_libunwind=yes + AC_DEFINE(HAVE_LIBUNWIND, 1, [Have libunwind support]) + fi +fi + + +if test "x$WESTON_NATIVE_BACKEND" = "x"; then + WESTON_NATIVE_BACKEND="drm-backend.so" +fi +AC_MSG_NOTICE([Weston's native backend: $WESTON_NATIVE_BACKEND]) +AC_DEFINE_UNQUOTED([WESTON_NATIVE_BACKEND], ["$WESTON_NATIVE_BACKEND"], + [The default backend to load, if not wayland nor x11.]) + +AC_ARG_ENABLE(demo-clients, + AS_HELP_STRING([--enable-demo-clients], + [install demo clients built with weston]),, + enable_demo_clients=no) +AM_CONDITIONAL(ENABLE_DEMO_CLIENTS, [test "x$enable_demo_clients" = "xyes"]) + +PKG_CHECK_MODULES(LCMS, lcms2, + [have_lcms=yes], [have_lcms=no]) +if test "x$have_lcms" = xyes; then + AC_DEFINE(HAVE_LCMS, 1, [Have lcms support]) +fi +AM_CONDITIONAL(HAVE_LCMS, [test "x$have_lcms" = xyes]) + +AC_PATH_PROG([wayland_scanner], [wayland-scanner]) +if test x$wayland_scanner = x; then + AC_MSG_ERROR([wayland-scanner is needed to compile weston]) +fi + +AC_CONFIG_FILES([Makefile + shared/Makefile + src/Makefile + src/xwayland/Makefile + src/version.h + src/weston.pc + clients/Makefile + wcap/Makefile + data/Makefile + protocol/Makefile + man/Makefile + tests/Makefile]) +AC_OUTPUT + +AC_MSG_RESULT([ + Native Backend ${WESTON_NATIVE_BACKEND} + setuid Install ${enable_setuid_install} + + Cairo Renderer ${with_cairo} + EGL ${enable_egl} + libxkbcommon ${enable_xkbcommon} + XWayland ${enable_xwayland} + + Build wcap utility ${enable_wcap_tools} + Build Tablet Shell ${enable_tablet_shell} + + weston-launch utility ${enable_weston_launch} + weston-launch systemd support ${have_systemd_login} + + DRM Compositor ${enable_drm_compositor} + X11 Compositor ${enable_x11_compositor} + Wayland Compositor ${enable_wayland_compositor} + Headless Compositor ${enable_headless_compositor} + RPI Compositor ${enable_rpi_compositor} + FBDEV Compositor ${enable_fbdev_compositor} + RDP Compositor ${enable_rdp_compositor} + + Raspberry Pi BCM headers ${have_bcm_host} + + Build Clients ${enable_clients} + Build EGL Clients ${have_cairo_egl} + Build Simple Clients ${enable_simple_clients} + Build Simple EGL Clients ${enable_simple_egl_clients} + + Install Demo Clients ${enable_demo_clients} + + Colord Support ${have_colord} + GLU Support ${have_glu} + LCMS2 Support ${have_lcms} + libwebp Support ${have_webp} + libunwind Support ${have_libunwind} + VA H.264 encoding Support ${have_libva} +]) diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 00000000..8ea50a66 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1 @@ +wayland.png diff --git a/data/COPYING b/data/COPYING new file mode 100644 index 00000000..430a864d --- /dev/null +++ b/data/COPYING @@ -0,0 +1,11 @@ +For the DMZ cursors: + +(c) 2007-2010 Novell, Inc. + +This work is licenced under the Creative Commons Attribution-Share Alike 3.0 +United States License. To view a copy of this licence, visit +http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative +Commons, 171 Second Street, Suite 300, San Francisco, California 94105, USA. + +The terminal icon is taken from the gnome-icon-theme collection which +is also distributed under the Creative Commons BY-SA 3.0 license. \ No newline at end of file diff --git a/data/Makefile.am b/data/Makefile.am new file mode 100644 index 00000000..a7cc9440 --- /dev/null +++ b/data/Makefile.am @@ -0,0 +1,19 @@ +westondatadir = $(datadir)/weston + +dist_westondata_DATA = \ + wayland.svg \ + $(wayland_icon_png) \ + pattern.png \ + terminal.png \ + border.png \ + icon_window.png \ + sign_close.png \ + sign_maximize.png \ + sign_minimize.png + +if HAVE_RSVG_CONVERT +wayland_icon_png = wayland.png + +wayland.png : $(top_srcdir)/data/wayland.svg + $(RSVG_CONVERT) -w 128 -h 128 $< -o $@ +endif diff --git a/data/border.png b/data/border.png new file mode 100644 index 00000000..6a3a8f9b Binary files /dev/null and b/data/border.png differ diff --git a/data/icon_window.png b/data/icon_window.png new file mode 100644 index 00000000..2587ca34 Binary files /dev/null and b/data/icon_window.png differ diff --git a/data/pattern.png b/data/pattern.png new file mode 100644 index 00000000..5ac8986d Binary files /dev/null and b/data/pattern.png differ diff --git a/data/sign_close.png b/data/sign_close.png new file mode 100644 index 00000000..741ea0b0 Binary files /dev/null and b/data/sign_close.png differ diff --git a/data/sign_maximize.png b/data/sign_maximize.png new file mode 100644 index 00000000..2443a4c0 Binary files /dev/null and b/data/sign_maximize.png differ diff --git a/data/sign_minimize.png b/data/sign_minimize.png new file mode 100644 index 00000000..f4f3d9d2 Binary files /dev/null and b/data/sign_minimize.png differ diff --git a/data/terminal.png b/data/terminal.png new file mode 100644 index 00000000..3c02dd21 Binary files /dev/null and b/data/terminal.png differ diff --git a/data/wayland.svg b/data/wayland.svg new file mode 100644 index 00000000..29cb8f06 --- /dev/null +++ b/data/wayland.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/man/.gitignore b/man/.gitignore new file mode 100644 index 00000000..6138c7d5 --- /dev/null +++ b/man/.gitignore @@ -0,0 +1,4 @@ +weston.1 +weston-drm.7 +weston.ini.5 + diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 00000000..e4abd8c4 --- /dev/null +++ b/man/Makefile.am @@ -0,0 +1,25 @@ +man_MANS = weston.1 weston.ini.5 + +if ENABLE_DRM_COMPOSITOR +man_MANS += weston-drm.7 +endif + +MAN_SUBSTS = \ + -e 's|__weston_native_backend__|$(WESTON_NATIVE_BACKEND)|g' \ + -e 's|__weston_modules_dir__|$(pkglibdir)|g' \ + -e 's|__version__|$(PACKAGE_VERSION)|g' + +SUFFIXES = .1 .5 .7 .man + +.man.1: + $(AM_V_GEN)$(SED) $(MAN_SUBSTS) < $< > $@ + +.man.5: + $(AM_V_GEN)$(SED) $(MAN_SUBSTS) < $< > $@ + +.man.7: + $(AM_V_GEN)$(SED) $(MAN_SUBSTS) < $< > $@ + +EXTRA_DIST = weston.man weston-drm.man weston.ini.man + +CLEANFILES = $(man_MANS) diff --git a/man/weston-drm.man b/man/weston-drm.man new file mode 100644 index 00000000..35d62ae6 --- /dev/null +++ b/man/weston-drm.man @@ -0,0 +1,130 @@ +.TH WESTON-DRM 7 "2012-11-27" "Weston __version__" +.SH NAME +weston-drm \- the DRM backend for Weston +.SH SYNOPSIS +.B weston-launch +.LP +.B weston --backend=drm-backend.so +. +.\" *************************************************************** +.SH DESCRIPTION +The DRM backend is the native Weston backend for systems that support +the Linux kernel DRM, kernel mode setting (KMS), and evdev input devices. +It is the recommended backend for desktop PCs, and aims to provide +the full Wayland experience with the "every frame is perfect" concept. +It also relies on the Mesa GBM interface. + +With the DRM backend, +.B weston +runs without any underlying windowing system. The backend uses the +Linux KMS API to detect connected monitors. Monitor hot-plugging is +supported. Input devices are found automatically by +.BR udev (7). +Compositing happens mainly in GL\ ES\ 2, initialized through EGL. It +is also possible to take advantage of hardware cursors and overlays, +when they exist and are functional. Full-screen surfaces will be +scanned out directly without compositing, when possible. +Hardware accelerated clients are supported via EGL. + +The backend chooses the DRM graphics device first based on seat id. +If seat identifiers are not set, it looks for the graphics device +that was used in boot. If that is not found, it finally chooses +the first DRM device returned by +.BR udev (7). +Combining multiple graphics devices are not supported yet. + +The DRM backend relies on +.B weston-launch +for managing input device access and DRM master status, so that +.B weston +can be run without root privileges. On switching away from the +virtual terminal (VT) hosting Weston, all input devices are closed and +the DRM master capability is dropped, so that other servers, +including +.BR Xorg (1), +can run on other VTs. On switching back to Weston's VT, input devices +and DRM master are re-acquired through the parent process +.BR weston-launch . +. +.\" *************************************************************** +.SH CONFIGURATION +. +The DRM backend uses the following entries from +.BR weston.ini . +.SS Section output +.TP +\fBname\fR=\fIconnector\fR +The KMS connector name identifying the output, for instance +.IR LVDS1 . +.TP +\fBmode\fR=\fImode\fR +Specify the video mode for the output. The argument +.I mode +can be one of the words +.BR off " to turn the output off, " +.BR preferred " to use the monitor's preferred video mode, or " +.BR current " to use the current video mode and avoid a mode switch." +It can also be a resolution as +\fIwidth\fBx\fIheight\fR, or a detailed mode line as below. +.TP +\fBmode\fR=\fIdotclock hdisp hsyncstart hsyncend htotal \ +vdisp vsyncstart vsyncend vtotal hflag vflag\fR +Use the given detailed mode line as the video mode for this output. +The definition is the same as in +.BR xorg.conf "(5), and " cvt (1) +can generate detailed mode lines. +.TP +\fBtransform\fR=\fItransform\fR +Transform for the output, which can be rotated in 90-degree steps +and possibly flipped. Possible values are +.BR normal ", " 90 ", " 180 ", " 270 ", " +.BR flipped ", " flipped-90 ", " flipped-180 ", and " flipped-270 . +. +.\" *************************************************************** +.SH OPTIONS +. +When the DRM backend is loaded, +.B weston +will understand the following additional command line options. +.TP +\fB\-\-connector\fR=\fIconnectorid\fR +Use the connector with id number +.I connectorid +as the only initial output. +.TP +.B \-\-current\-mode +By default, use the current video mode of all outputs, instead of +switching to the monitor preferred mode. +.TP +\fB\-\-seat\fR=\fIseatid\fR +Use graphics and input devices designated for seat +.I seatid +instead of the default seat +.BR seat0 . +.TP +\fB\-\-tty\fR=\fIx\fR +Launch Weston on tty +.I x +instead of using the current tty. +. +.\" *************************************************************** +.SH ENVIRONMENT +. +.TP +.B WESTON_TTY_FD +The file descriptor (integer) of the opened tty where +.B weston +will run. Set by +.BR weston-launch . +.TP +.B WESTON_LAUNCHER_SOCK +The file descriptor (integer) where +.B weston-launch +is listening. Automatically set by +.BR weston-launch . +. +.\" *************************************************************** +.SH "SEE ALSO" +.BR weston (1) +.\".BR weston-launch (1), +.\".BR weston.ini (5) diff --git a/man/weston.ini.man b/man/weston.ini.man new file mode 100644 index 00000000..c5ec3218 --- /dev/null +++ b/man/weston.ini.man @@ -0,0 +1,378 @@ +.\" shorthand for double quote that works everywhere. +.ds q \N'34' +.TH weston.ini 5 "2013-01-17" "Weston __version__" +.SH NAME +weston.ini \- configuration file for +.B Weston +\- the reference Wayland +compositor +.SH INTRODUCTION +.B Weston +obtains configuration from its command line parameters and the configuration +file described here. +.SH DESCRIPTION +.B Weston +uses a configuration file called +.I weston.ini +for its setup. +The +.I weston.ini +configuration file is searched for in one of the following places when the +server is started: +.PP +.RS 4 +.nf +.BR "$XDG_CONFIG_HOME/weston.ini " "(if $XDG_CONFIG_HOME is set)" +.BR "$HOME/.config/weston.ini " "(if $HOME is set)" +.B "weston/weston.ini in each" +.BR "\ \ \ \ $XDG_CONFIG_DIR " "(if $XDG_CONFIG_DIRS is set)" +.BR "/etc/xdg/weston/weston.ini " "(if $XDG_CONFIG_DIRS is not set)" +.BR "/weston.ini " "(if no variables were set)" +.fi +.RE +.PP +where environment variable +.B $HOME +is the user's home directory, and +.B $XDG_CONFIG_HOME +is the user specific configuration directory, and +.B $XDG_CONFIG_DIRS +is a colon +.B ':' +delimited listed of configuration base directories, such as +.BR /etc/xdg-foo:/etc/xdg . +.PP +The +.I weston.ini +file is composed of a number of sections which may be present in any order, or +omitted to use default configuration values. Each section has the form: +.PP +.RS 4 +.nf +.BI [ SectionHeader ] +.RI Key1=Value1 +.RI Key2=Value2 + ... +.fi +.RE +.PP +The spaces are significant. +Comment lines are ignored: +.PP +.RS 4 +.nf +.IR "#comment" +.fi +.RE +.PP +The section headers are: +.PP +.RS 4 +.nf +.BR "core " "The core modules" +.BR "shell " "Desktop customization" +.BR "launcher " "Add launcher to the panel" +.BR "screensaver " "Screensaver selection" +.BR "output " "Output configuration" +.BR "input-method " "Onscreen keyboard input" +.BR "keyboard " "Keyboard layouts" +.BR "terminal " "Terminal application options" +.BR "xwayland " "XWayland options" +.fi +.RE +.PP +Possible value types are string, signed and unsigned 32-bit +integer, and boolean. Strings must not be quoted, do not support any +escape sequences, and run till the end of the line. Integers can +be given in decimal (e.g. 123), octal (e.g. 0173), and hexadecimal +(e.g. 0x7b) form. Boolean values can be only 'true' or 'false'. +.RE +.SH "CORE SECTION" +The +.B core +section is used to select the startup compositor modules. +.TP 7 +.BI "modules=" desktop-shell.so,xwayland.so +specifies the modules to load (string). Available modules in the +.IR "__weston_modules_dir__" +directory are: +.PP +.RS 10 +.nf +.BR desktop-shell.so +.BR tablet-shell.so +.BR xwayland.so +.fi +.RE +.RS +.PP + +.SH "SHELL SECTION" +The +.B shell +section is used to customize the compositor. Some keys may not be handled by +different shell plugins. +.PP +The entries that can appear in this section are: +.TP 7 +.BI "background-image=" file +sets the path for the background image file (string). +.TP 7 +.BI "background-type=" tile +determines how the background image is drawn (string). Can be +.BR scale ", " scale-crop " or " tile " (default)." +Scale means scaled to fit the output precisely, not preserving aspect ratio. +Scale-crop preserves aspect ratio, scales the background image just big +enough to cover the output, and centers it. The image ends up cropped from +left and right, or top and bottom, if the aspect ratio does not match the +output. Tile repeats the background image to fill the output. +.TP 7 +.BI "background-color=" 0xAARRGGBB +sets the color of the background (unsigned integer). The hexadecimal +digit pairs are in order alpha, red, green, and blue. +.TP 7 +.BI "panel-color=" 0xAARRGGBB +sets the color of the panel (unsigned integer). The hexadecimal +digit pairs are in order transparency, red, green, and blue. Examples: +.PP +.RS 10 +.nf +.BR "0xffff0000 " "Red" +.BR "0xff00ff00 " "Green" +.BR "0xff0000ff " "Blue" +.BR "0x00ffffff " "Fully transparent" +.fi +.RE +.TP 7 +.BI "locking=" true +enables screen locking (boolean). +.TP 7 +.BI "animation=" zoom +sets the effect used for opening new windows (string). Can be +.B zoom, +.B fade, +.B none. +By default, no animation is used. +.TP 7 +.BI "startup-animation=" fade +sets the effect used for opening new windows (string). Can be +.B fade, +.B none. +By default, the fade animation is used. +.TP 7 +.BI "binding-modifier=" ctrl +sets the modifier key used for common bindings (string), such as moving +surfaces, resizing, rotating, switching, closing and setting the transparency +for windows, controlling the backlight and zooming the desktop. Possible values: +ctrl, alt, super (default) +.TP 7 +.BI "num-workspaces=" 6 +defines the number of workspaces (unsigned integer). The user can switch +workspaces by using the +binding+F1, F2 keys. If this key is not set, fall back to one workspace. +.TP 7 +.BI "cursor-theme=" theme +sets the cursor theme (string). +.TP 7 +.BI "cursor-size=" 24 +sets the cursor size (unsigned integer). +.TP 7 +.BI "lockscreen-icon=" path +sets the path to lock screen icon image (string). (tablet shell only) +.TP 7 +.BI "lockscreen=" path +sets the path to lock screen background image (string). (tablet shell only) +.TP 7 +.BI "homescreen=" path +sets the path to home screen background image (string). (tablet shell only) +.RE +.SH "LAUNCHER SECTION" +There can be multiple launcher sections, one for each launcher. +.TP 7 +.BI "icon=" icon +sets the path to icon image (string). Svg images are not currently supported. +.TP 7 +.BI "path=" program +sets the path to the program that is run by clicking on this launcher (string). +It is possible to pass arguments and environment variables to the program. For +example: +.nf +.in +4n + +path=GDK_BACKEND=wayland gnome-terminal --full-screen +.in +.fi +.PP +.RE +.SH "SCREENSAVER SECTION" +The +.B screensaver +section is used to select and schedule a screensaver. +The +.B screensaver +section is optional, as are all of the entries that may be specified in +it. +.TP 7 +.BI "path=" /usr/libexec/weston-screensaver +This instructs the compositor to use the selected screensaver client on a given +path (string). If this line is missing or commented out, the screensaver in +.B "weston(1)" +is disabled. +.RE +.TP 7 +.BI "duration=" 600 +The idle time in seconds until the screensaver disappears in order to save power +(unsigned integer). +.SH "OUTPUT SECTION" +There can be multiple output sections, each corresponding to one output. It is +currently only recognized by the drm and x11 backends. +.TP 7 +.BI "name=" name +sets a name for the output (string). The backend uses the name to +identify the output. All X11 output names start with a letter X. The available +output names for DRM backend are listed in the +.B "weston-launch(1)" +output. +Examples of usage: +.PP +.RS 10 +.nf +.BR "LVDS1 " "DRM backend, Laptop internal panel no.1" +.BR "VGA1 " "DRM backend, VGA connector no.1" +.BR "X1 " "X11 backend, X window no.1" +.fi +.RE +.RS +.PP +See +.B "weston-drm(7)" +for more details. +.RE +.TP 7 +.BI "mode=" mode +sets the output mode (string). The mode parameter is handled differently +depending on the backend. On the X11 backend, it just sets the WIDTHxHEIGHT of +the weston window. +The DRM backend accepts different modes: +.PP +.RS 10 +.nf +.BR "WIDTHxHEIGHT " "Resolution size width and height in pixels" +.BR "preferred " "Uses the preferred mode" +.BR "current " "Uses the current crt controller mode" +.BR "off " "Disables the output" +.fi +.RE +.RS +.PP +Optionally, an user may specify a modeline, such as: +.PP +.nf +.in +4n +.nf +173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync +.fi +.in +.PP +It consists of the refresh rate in Hz, horizontal and vertical resolution, +options for horizontal and vertical synchronisation. The program +.B "cvt(1)" +can provide suitable modeline string. +.RE +.TP 7 +.BI "transform=" normal +The transformation applied to screen output (string). The transform key can +be one of the following 8 strings: +.PP +.RS 10 +.nf +.BR "normal " "Normal output." +.BR "90 " "90 degrees clockwise." +.BR "180 " "Upside down." +.BR "270 " "90 degrees counter clockwise." +.BR "flipped " "Horizontally flipped" +.BR "flipped-90 " "Flipped and 90 degrees clockwise" +.BR "flipped-180 " "Flipped upside down" +.BR "flipped-270 " "Flipped and 90 degrees counter clockwise" +.fi +.RE +.TP 7 +.BI "seat=" name +The logical seat name that that this output should be associated with. If this +is set then the seat's input will be confined to the output that has the seat +set on it. The expectation is that this functionality will be used in a +multiheaded environment with a single compositor for multiple output and input +configurations. The default seat is called "default" and will always be +present. This seat can be constrained like any other. +.RE +.SH "INPUT-METHOD SECTION" +.TP 7 +.BI "path=" "/usr/libexec/weston-keyboard" +sets the path of the on screen keyboard input method (string). +.RE +.RE +.SH "KEYBOARD SECTION" +This section contains the following keys: +.TP 7 +.BI "keymap_rules=" "evdev" +sets the keymap rules file (string). Used to map layout and model to input +device. +.RE +.RE +.TP 7 +.BI "keymap_model=" "pc105" +sets the keymap model (string). See the Models section in +.B "xkeyboard-config(7)." +.RE +.RE +.TP 7 +.BI "keymap_layout=" "us,de,gb" +sets the comma separated list of keyboard layout codes (string). See the +Layouts section in +.B "xkeyboard-config(7)." +.RE +.RE +.TP 7 +.BI "keymap_variant=" "euro,,intl" +sets the comma separated list of keyboard layout variants (string). The number +of variants must be the same as the number of layouts above. See the Layouts +section in +.B "xkeyboard-config(7)." +.RE +.RE +.TP 7 +.BI "keymap_options=" "grp:alt_shift_toggle,grp_led:scroll" +sets the keymap options (string). See the Options section in +.B "xkeyboard-config(7)." +.RE +.RE +.SH "TERMINAL SECTION" +Contains settings for the weston terminal application (weston-terminal). It +allows to customize the font and shell of the command line interface. +.TP 7 +.BI "font=" "DejaVu Sans Mono" +sets the font of the terminal (string). For a good experience it is recommend +to use monospace fonts. In case the font is not found, the default one is used. +.RE +.RE +.TP 7 +.BI "font-size=" "14" +sets the size of the terminal font (unsigned integer). +.RE +.RE +.TP 7 +.BI "term=" "xterm-256color" +The terminal shell (string). Sets the $TERM variable. +.RE +.RE +.SH "XWAYLAND SECTION" +.TP 7 +.BI "path=" "/usr/bin/Xorg" +sets the path to the xserver to run (string). +.RE +.RE +.SH "SEE ALSO" +.BR weston (1), +.BR weston-launch (1), +.BR weston-drm (7), +.BR xkeyboard-config (7) diff --git a/man/weston.man b/man/weston.man new file mode 100644 index 00000000..39d854be --- /dev/null +++ b/man/weston.man @@ -0,0 +1,283 @@ +.TH WESTON 1 "2012-11-27" "Weston __version__" +.SH NAME +weston \- the reference Wayland server +.SH SYNOPSIS +.B weston +. +.\" *************************************************************** +.SH DESCRIPTION +.B weston +is the reference implementation of a Wayland server. A Wayland server is a +display server, a window manager, and a compositor all in one. Weston has +several backends as loadable modules: it can run on Linux KMS (kernel +modesetting via DRM), as an X client, or inside another Wayland server +instance. + +Weston supports fundamentally different graphical user interface paradigms via +shell plugins. Two plugins are provided: the desktop shell, and the tablet +shell. + +When weston is started as the first windowing system (i.e. not under X nor +under another Wayland server), it should be done with the command +.B weston-launch +to set up proper privileged access to devices. + +Weston also supports X clients via +.BR XWayland ", see below." +. +.\" *************************************************************** +.SH BACKENDS +.TP +.I drm-backend.so +The DRM backend uses Linux KMS for output and evdev devices for input. +It supports multiple monitors in a unified desktop with DPMS. See +.BR weston-drm (7), +if installed. +.TP +.I wayland-backend.so +The Wayland backend runs on another Wayland server, a different Weston +instance, for example. Weston shows up as a single desktop window on +the parent server. +.TP +.I x11-backend.so +The X11 backend runs on an X server. Each Weston output becomes an +X window. This is a cheap way to test multi-monitor support of a +Wayland shell, desktop, or applications. +. +.\" *************************************************************** +.SH SHELLS +.TP +Desktop shell +Desktop shell is like a modern X desktop environment, concentrating +on traditional keyboard and mouse user interfaces and the familiar +desktop-like window management. Desktop shell consists of the +shell plugin +.I desktop-shell.so +and the special client +.B weston-desktop-shell +which provides the wallpaper, panel, and screen locking dialog. +.TP +Tablet shell +Tablet shell is a graphical user interface aimed for tablet-like +devices, where usually the only input method is a touch screen. +It does not support freely floating windows or many other desktop +features, but intends to provide a natural interface on tablets. +Tablet shell consists of the shell plugin +.I tablet-shell.so +and the special client +.B weston-tablet-shell +which provides the basic user interface. +. +.\" *************************************************************** +.SH XWAYLAND +XWayland requires a special X.org server to be installed. This X server will +connect to a Wayland server as a Wayland client, and X clients will connect to +the X server. XWayland provides backwards compatibility to X applications in a +Wayland stack. + +XWayland is activated by instructing +.BR weston " to load " xwayland.so " module, see " EXAMPLES . +Weston starts listening on a new X display socket, and exports it in the +environment variable +.BR DISPLAY . +When the first X client connects, Weston launches a special X server as a +Wayland client to handle the X client and all future X clients. + +It has also its own X window manager where cursor themes and sizes can be +chosen using +.BR XCURSOR_PATH +and +.BR XCURSOR_SIZE " environment variables. See " ENVIRONMENT . +. +.\" *************************************************************** +.SH OPTIONS +. +.SS Weston core options: +.TP +\fB\-\^B\fR\fIbackend.so\fR, \fB\-\-backend\fR=\fIbackend.so\fR +Load +.I backend.so +instead of the default backend. The file is searched for in +.IR "__weston_modules_dir__" , +or you can pass an absolute path. The default backend is +.I __weston_native_backend__ +unless the environment suggests otherwise, see +.IR DISPLAY " and " WAYLAND_DISPLAY . +.TP +.BR \-\-version +Print the program version. +.TP +.BR \-\^h ", " \-\-help +Print a summary of command line options, and quit. +.TP +\fB\-\^i\fR\fIN\fR, \fB\-\-idle\-time\fR=\fIN\fR +Set the idle timeout to +.I N +seconds. The default timeout is 300 seconds. When there has not been any +user input for the idle timeout, Weston enters an inactive mode. The +screen fades to black, and depending on the shell in use, a screensaver +may activate, monitors may switch off, and the shell may lock the session. +A value of 0 effectively disables the timeout. +.TP +\fB\-\-log\fR=\fIfile.log\fR +Append log messages to the file +.I file.log +instead of writing them to stderr. +.TP +\fB\-\-modules\fR=\fImodule1.so,module2.so\fR +Load the comma-separated list of modules. Only used by the test +suite. The file is searched for in +.IR "__weston_modules_dir__" , +or you can pass an absolute path. +.TP +\fB\-\^S\fR\fIname\fR, \fB\-\-socket\fR=\fIname\fR +Weston will listen in the Wayland socket called +.IR name . +Weston will export +.B WAYLAND_DISPLAY +with this value in the environment for all child processes to allow them to +connect to the right server automatically. +.SS DRM backend options: +See +.BR weston-drm (7). +. +.SS Wayland backend options: +.TP +\fB\-\-display\fR=\fIdisplay\fR +Name of the Wayland display to connect to, see also +.I WAYLAND_DISPLAY +of the environment. +.TP +\fB\-\-width\fR=\fIW\fR, \fB\-\-height\fR=\fIH\fR +Make the desktop size +.IR W x H " pixels." +. +.SS X11 backend options: +.TP +.B \-\-fullscreen +.TP +.B \-\-no\-input +Do not provide any input devices. Used for testing input-less Weston. +.TP +\fB\-\-output\-count\fR=\fIN\fR +Create +.I N +X windows to emulate the same number of outputs. +.TP +\fB\-\-width\fR=\fIW\fR, \fB\-\-height\fR=\fIH\fR +Make the default size of each X window +.IR W x H " pixels." +.TP +.B \-\-use\-pixman +Use the pixman renderer. By default weston will try to use EGL and +GLES2 for rendering. Passing this option will make weston use the +pixman library for software compsiting. +. +.\" *************************************************************** +.SH FILES +. +If the environment variable is set, the configuration file is read +from the respective path, or the current directory if neither is set. +.PP +.BI $XDG_CONFIG_HOME /weston.ini +.br +.BI $HOME /.config/weston.ini +.br +.I ./weston.ini +.br +. +.\" *************************************************************** +.SH ENVIRONMENT +. +.TP +.B DISPLAY +The X display. If +.B DISPLAY +is set, and +.B WAYLAND_DISPLAY +is not set, the default backend becomes +.IR x11-backend.so . +.TP +.B WAYLAND_DEBUG +If set to any value, causes libwayland to print the live protocol +to stderr. +.TP +.B WAYLAND_DISPLAY +The name of the display (socket) of an already running Wayland server, without +the path. The directory path is always taken from +.BR XDG_RUNTIME_DIR . +If +.B WAYLAND_DISPLAY +is not set, the socket name is "wayland-0". + +If +.B WAYLAND_DISPLAY +is already set, the default backend becomes +.IR wayland-backend.so . +This allows launching Weston as a nested server. +.TP +.B WAYLAND_SOCKET +For Wayland clients, holds the file descriptor of an open local socket +to a Wayland server. +.TP +.B XCURSOR_PATH +Set the list of paths to look for cursors in. It changes both +libwayland-cursor and libXcursor, so it affects both Wayland and X11 based +clients. See +.B xcursor +(3). +.TP +.B XCURSOR_SIZE +This variable can be set for choosing an specific size of cursor. Affect +Wayland and X11 clients. See +.B xcursor +(3). +.TP +.B XDG_CONFIG_HOME +If set, specifies the directory where to look for +.BR weston.ini . +.TP +.B XDG_RUNTIME_DIR +The directory for Weston's socket and lock files. +Wayland clients will automatically use this. +. +.\" *************************************************************** +.SH DIAGNOSTICS +Weston has a segmentation fault handler, that attempts to restore +the virtual console or ungrab X before raising +.BR SIGTRAP . +If you run +.BR weston " under " gdb (1) +from an X11 terminal or a different virtual terminal, and tell gdb +.IP +handle SIGSEGV nostop +.PP +This will allow weston to switch back to gdb on crash and then +gdb will catch the crash with SIGTRAP. +. +.\" *************************************************************** +.SH BUGS +Bugs should be reported to the freedesktop.org bugzilla at +https://bugs.freedesktop.org with product "Wayland" and +component "weston". +. +.\" *************************************************************** +.SH WWW +http://wayland.freedesktop.org/ +. +.\" *************************************************************** +.SH EXAMPLES +.IP "Launch Weston with the DRM backend on a VT" +weston-launch +.IP "Launch Weston with the DRM backend and XWayland support" +weston-launch -- --modules=xwayland.so +.IP "Launch Weston (wayland-1) nested in another Weston instance (wayland-0)" +WAYLAND_DISPLAY=wayland-0 weston -Swayland-1 +.IP "From an X terminal, launch Weston with the x11 backend" +weston +. +.\" *************************************************************** +.SH "SEE ALSO" +.BR weston-drm (7) +.\".BR weston-launch (1), +.\".BR weston.ini (5) diff --git a/notes.txt b/notes.txt new file mode 100644 index 00000000..e49052b8 --- /dev/null +++ b/notes.txt @@ -0,0 +1,77 @@ +This file is a collection of informal notes, with references to where +they were originally written. Each note should have a source and date +mentioned. Let's keep these in date order, newest first. + + + +----------------------------------------------------------------------- +2012-10-23; Pekka Paalanen +http://lists.freedesktop.org/archives/wayland-devel/2012-October/005969.html + +For anyone wanting to port or write their own window manager to Wayland: + +Most likely you have a desktop window manager. A quick way to get +started, is to fork Weston's desktop-shell plugin and start hacking it. +Qt could be another good choice, but I am not familiar with it. + +You also need to understand some concepts. I'm repeating things I wrote +to the wayland-devel list earlier, a little rephrased. + +We need to distinguish three different things here (towards Wayland +clients): + +- compositors (servers) + All Wayland compositors are indistinguishable by definition, + since they are Wayland compositors. They only differ in the + global interfaces they advertise, and for general purpose + compositors, we should aim to support the same minimum set of + globals everywhere. For instance, all desktop compositors + should implement wl_shell. In X, this component corresponds to + the X server with a built-in compositing manager. + +- shells + This is a new concept compared to an X stack. A shell defines + how a user and applications interact. The most familiar is a + desktop (environment). If KDE, Gnome, and XFCE are desktop + environments, they all fall under the *same* shell: the desktop + shell. You can have applications in windows, several visible at + the same time, you have keyboards and mice, etc. + + An example of something that is not a desktop shell + could be a TV user interface. TV is profoundly different: + usually no mouse, no keyboard, but you have a remote control + with some buttons. Freely floating windows probably do not make + sense. You may have picture-in-picture, but usually not several + applications showing at once. Most importantly, trying to run + desktop applications here does not work due to the + incompatible application and user interface paradigms. + + On protocol level, a shell is the public shell interface(s), + currently for desktop it is the wl_shell. + +- "window managers" + The X Window Managers correspond to different wl_shell + implementations, not different shells, since they pratically + all deal with a desktop environment. You also want all desktop + applications to work with all window managers, so you need to + implement wl_shell anyway. + +I understand there could be special purpose X Window Managers, that +would better correspond to their own shells. These window managers +might not implement e.g. EWMH by the spec. + +When you implement your own window manager, you want to keep the public +desktop shell interface (wl_shell). You can offer new public +interfaces, too, but keep in mind, that someone needs to make +applications use them. + +In Weston, a shell implementation has two parts: a weston plugin, and a +special client. For desktop shell (wl_shell) these are src/shell.c and +clients/desktop-shell.c. The is also a private protocol extension that +these two can explicitly communicate with. + +The plugin does window management, and the client does most of user +interaction: draw backgrounds, panels, buttons, lock screen dialog, +basically everything that is on screen. + +----------------------------------------------------------------------- diff --git a/protocol/Makefile.am b/protocol/Makefile.am new file mode 100644 index 00000000..924e48f3 --- /dev/null +++ b/protocol/Makefile.am @@ -0,0 +1,11 @@ +EXTRA_DIST = \ + desktop-shell.xml \ + screenshooter.xml \ + tablet-shell.xml \ + xserver.xml \ + text.xml \ + input-method.xml \ + workspaces.xml \ + subsurface.xml \ + text-cursor-position.xml \ + wayland-test.xml diff --git a/protocol/desktop-shell.xml b/protocol/desktop-shell.xml new file mode 100644 index 00000000..65e44a73 --- /dev/null +++ b/protocol/desktop-shell.xml @@ -0,0 +1,112 @@ + + + + + Traditional user interfaces can rely on this interface to define the + foundations of typical desktops. Currently it's possible to set up + background, panels and locking surfaces. + + + + + + + + + + + + + + + + + + + + + The surface set by this request will receive a fake + pointer.enter event during grabs at position 0, 0 and is + expected to set an appropriate cursor image as described by + the grab_cursor event sent just before the enter event. + + + + + + + Tell the server, that enough desktop elements have been drawn + to make the desktop look ready for use. During start-up, the + server can wait for this request with a black screen before + starting to fade in the desktop, for instance. If the client + parts of a desktop take a long time to initialize, we avoid + showing temporary garbage. + + + + + + + + + + + + + + Tell the shell we want it to create and set the lock surface, which is + a GUI asking the user to unlock the screen. The lock surface is + announced with 'set_lock_surface'. Whether or not the shell actually + implements locking, it MUST send 'unlock' request to let the normal + desktop resume. + + + + + + This event will be sent immediately before a fake enter event on the + grab surface. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Only one client can bind this interface at a time. + + + + + A screensaver surface is normally hidden, and only visible after an + idle timeout. + + + + + + + + diff --git a/protocol/input-method.xml b/protocol/input-method.xml new file mode 100644 index 00000000..70afdcb1 --- /dev/null +++ b/protocol/input-method.xml @@ -0,0 +1,273 @@ + + + + Copyright © 2012, 2013 Intel Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + + Corresponds to a text model on input method side. An input method context + is created on text mode activation on the input method side. It allows to + receive information about the text model from the application via events. + Input method contexts do not keep state after deactivation and should be + destroyed after deactivation is handled. + + Text is generally UTF-8 encoded, indices and lengths are in bytes. + + Serials are used to synchronize the state between the text input and + an input method. New serials are sent by the text input in the + commit_state request and are used by the input method to indicate + the known text input state in events like preedit_string, commit_string, + and keysym. The text input can then ignore events from the input method + which are based on an outdated state (for example after a reset). + + + + + Send the commit string text for insertion to the application. + + The text to commit could be either just a single character after a key + press or the result of some composing (pre-edit). It could be also an + empty text when some text should be removed (see + delete_surrounding_text) or when the input cursor should be moved (see + cursor_position). + + Any previously set composing text will be removed. + + + + + + + Send the pre-edit string text to the application text input. + + The commit text can be used to replace the preedit text on reset (for + example on unfocus). + + Also previously sent preedit_style and preedit_cursor requests are + processed bt the text_input also. + + + + + + + + Sets styling information on composing text. The style is applied for + length in bytes from index relative to the beginning of + the composing text (as byte offset). Multiple styles can + be applied to a composing text. + + This request should be sent before sending preedit_string request. + + + + + + + + Sets the cursor position inside the composing text (as byte offset) + relative to the start of the composing text. + + When index is negative no cursor should be displayed. + + This request should be sent before sending preedit_string request. + + + + + + + + This request will be handled on text_input side as part of a directly + following commit_string request. + + + + + + + Sets the cursor and anchor to a new position. Index is the new cursor + position in bytess (when >= 0 relative to the end of inserted text + else relative to beginning of inserted text). Anchor is the new anchor + position in bytes (when >= 0 relative to the end of inserted text, else + relative to beginning of inserted text). When there should be no + selected text anchor should be the same as index. + + This request will be handled on text_input side as part of a directly + following commit_string request. + + + + + + + + + + Notify when a key event was sent. Key events should not be used for + normal text input operations, which should be done with commit_string, + delete_surrounfing_text, etc. The key event follows the wl_keyboard key + event convention. Sym is a XKB keysym, state a wl_keyboard key_state. + + + + + + + + + + Allows an input method to receive hardware keyboard input and process + key events to generate text events (with pre-edit) over the wire. This + allows input methods which compose multiple key events for inputting + text like it is done for CJK languages. + + + + + + Should be used when filtering key events with grab_keyboard. + + When the wl_keyboard::key event is not processed by the input + method itself and should be sent to the client instead, forward it + with this request. The arguments should be the ones from the + wl_keyboard::key event. + + For generating custom key events use the keysym request instead. + + + + + + + + + Should be used when filtering key events with grab_keyboard. + + When the wl_keyboard::modifiers event should be also send to the + client, forward it with this request. The arguments should be the ones + from the wl_keyboard::modifiers event. + + + + + + + + + + + + + + + + + + The plain surrounding text around the input position. Cursor is the + position in bytes within the surrounding text relative to the beginning + of the text. Anchor is the position in bytes of the selection anchor + within the surrounding text relative to the beginning of the text. If + there is no selected text anchor is the same as cursor. + + + + + + + + + + + + + + + + + + + + + + + + + + An input method object is responsible to compose text in response to + input from hardware or virtual keyboards. There is one input method + object per seat. On activate there is a new input method context object + created which allows the input method to communicate with the text model. + + + + A text model was activated. Creates an input method context object + which allows communication with the text model. + + + + + + The text model corresponding to the context argument was deactivated. + The input method context should be destroyed after deactivation is + handled. + + + + + + + + Only one client can bind this interface at a time. + + + + + + + + + + + + + + + + A keybaord surface is only shown, when a text model is active + + + + + + + + An overlay panel is shown near the input cursor above the application + window when a text model is active. + + + + diff --git a/protocol/screenshooter.xml b/protocol/screenshooter.xml new file mode 100644 index 00000000..76e3c85a --- /dev/null +++ b/protocol/screenshooter.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/protocol/subsurface.xml b/protocol/subsurface.xml new file mode 100644 index 00000000..9e4a658d --- /dev/null +++ b/protocol/subsurface.xml @@ -0,0 +1,244 @@ + + + + + Copyright © 2012-2013 Collabora, Ltd. + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The global interface exposing sub-surface compositing capabilities. + A wl_surface, that has sub-surfaces associated, is called the + parent surface. Sub-surfaces can be arbitrarily nested and create + a tree of sub-surfaces. + + The root surface in a tree of sub-surfaces is the main + surface. The main surface cannot be a sub-surface, because + sub-surfaces must always have a parent. + + A main surface with its sub-surfaces forms a (compound) window. + For window management purposes, this set of wl_surface objects is + to be considered as a single window, and it should also behave as + such. + + The aim of sub-surfaces is to offload some of the compositing work + within a window from clients to the compositor. A prime example is + a video player with decorations and video in separate wl_surface + objects. This should allow the compositor to pass YUV video buffer + processing to dedicated overlay hardware when possible. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other + objects, wl_subsurface objects included. + + + + + + + + + + Create a sub-surface interface for the given surface, and + associate it with the given parent surface. This turns a + plain wl_surface into a sub-surface. + + The to-be sub-surface must not already have a dedicated + purpose, like any shell surface type, cursor image, drag icon, + or sub-surface. Otherwise a protocol error is raised. + + + + + + + + + + + An additional interface to a wl_surface object, which has been + made a sub-surface. A sub-surface has one parent surface. A + sub-surface's size and position are not limited to that of the parent. + Particularly, a sub-surface is not automatically clipped to its + parent's area. + + A sub-surface becomes mapped, when a non-NULL wl_buffer is applied + and the parent surface is mapped. The order of which one happens + first is irrelevant. A sub-surface is hidden if the parent becomes + hidden, or if a NULL wl_buffer is applied. These rules apply + recursively through the tree of surfaces. + + The behaviour of wl_surface.commit request on a sub-surface + depends on the sub-surface's mode. The possible modes are + synchronized and desynchronized, see methods + wl_subsurface.set_sync and wl_subsurface.set_desync. Synchronized + mode caches the wl_surface state to be applied when the parent's + state gets applied, and desynchronized mode applies the pending + wl_surface state directly. A sub-surface is initially in the + synchronized mode. + + Sub-surfaces have also other kind of state, which is managed by + wl_subsurface requests, as opposed to wl_surface requests. This + state includes the sub-surface position relative to the parent + surface (wl_subsurface.set_position), and the stacking order of + the parent and its sub-surfaces (wl_subsurface.place_above and + .place_below). This state is applied when the parent surface's + wl_surface state is applied, regardless of the sub-surface's mode. + As the exception, set_sync and set_desync are effective immediately. + + The main surface can be thought to be always in desynchronized mode, + since it does not have a parent in the sub-surfaces sense. + + Even if a sub-surface is in desynchronized mode, it will behave as + in synchronized mode, if its parent surface behaves as in + synchronized mode. This rule is applied recursively throughout the + tree of surfaces. This means, that one can set a sub-surface into + synchronized mode, and then assume that all its child and grand-child + sub-surfaces are synchronized, too, without explicitly setting them. + + If the wl_surface associated with the wl_subsurface is destroyed, the + wl_subsurface object becomes inert. Note, that destroying either object + takes effect immediately. If you need to synchronize the removal + of a sub-surface to the parent surface update, unmap the sub-surface + first by attaching a NULL wl_buffer, update parent, and then destroy + the sub-surface. + + If the parent wl_surface object is destroyed, the sub-surface is + unmapped. + + + + + The sub-surface interface is removed from the wl_surface object + that was turned into a sub-surface with + wl_subcompositor.get_subsurface request. The wl_surface's association + to the parent is deleted, and the wl_surface loses its role as + a sub-surface. The wl_surface is unmapped. + + + + + + + + + + This schedules a sub-surface position change. + The sub-surface will be moved so, that its origin (top-left + corner pixel) will be at the location x, y of the parent surface + coordinate system. The coordinates are not restricted to the parent + surface area. Negative values are allowed. + + The next wl_surface.commit on the parent surface will reset + the sub-surface's position to the scheduled coordinates. + + The initial position is 0, 0. + + + + + + + + + This sub-surface is taken from the stack, and put back just + above the reference surface, changing the z-order of the sub-surfaces. + The reference surface must be one of the sibling surfaces, or the + parent surface. Using any other surface, including this sub-surface, + will cause a protocol error. + + The z-order is double-buffered state, and will be applied on the + next commit of the parent surface. + See wl_surface.commit and wl_subcompositor.get_subsurface. + + A new sub-surface is initially added as the top-most in the stack + of its siblings and parent. + + + + + + + + The sub-surface is placed just below of the reference surface. + See wl_subsurface.place_above. + + + + + + + + Change the commit behaviour of the sub-surface to synchronized + mode, also described as the parent dependant mode. + + In synchronized mode, wl_surface.commit on a sub-surface will + accumulate the committed state in a cache, but the state will + not be applied and hence will not change the compositor output. + The cached state is applied to the sub-surface immediately after + the parent surface's state is applied. This ensures atomic + updates of the parent and all its synchronized sub-surfaces. + Applying the cached state will invalidate the cache, so further + parent surface commits do not (re-)apply old state. + + See wl_subsurface for the recursive effect of this mode. + + + + + + Change the commit behaviour of the sub-surface to desynchronized + mode, also described as independent or freely running mode. + + In desynchronized mode, wl_surface.commit on a sub-surface will + apply the pending state directly, without caching, as happens + normally with a wl_surface. Calling wl_surface.commit on the + parent surface has no effect on the sub-surface's wl_surface + state. This mode allows a sub-surface to be updated on its own. + + If cached state exists when wl_surface.commit is called in + desynchronized mode, the pending state is added to the cached + state, and applied as whole. This invalidates the cache. + + Note: even if a sub-surface is set to desynchronized, a parent + sub-surface may override it to behave as synchronized. For details, + see wl_subsurface. + + If a surface's parent surface behaves as desynchronized, then + the cached state is applied on set_desync. + + + + + diff --git a/protocol/tablet-shell.xml b/protocol/tablet-shell.xml new file mode 100644 index 00000000..10f17568 --- /dev/null +++ b/protocol/tablet-shell.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/protocol/text-cursor-position.xml b/protocol/text-cursor-position.xml new file mode 100644 index 00000000..0fbc54e7 --- /dev/null +++ b/protocol/text-cursor-position.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/protocol/text.xml b/protocol/text.xml new file mode 100644 index 00000000..1b5284dc --- /dev/null +++ b/protocol/text.xml @@ -0,0 +1,346 @@ + + + + + Copyright © 2012, 2013 Intel Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + An object used for text input. Adds support for text input and input + methods to applications. A text-input object is created from a + wl_text_input_manager and corresponds typically to a text entry in an + application. + Requests are used to activate/deactivate the text-input object and set + state information like surrounding and selected text or the content type. + The information about entered text is sent to the text-input object via + the pre-edit and commit events. Using this interface removes the need + for applications to directly process hardware key events and compose text + out of them. + + Text is generally UTF-8 encoded, indices and lengths are in bytes. + + Serials are used to synchronize the state between the text input and + an input method. New serials are sent by the text input in the + commit_state request and are used by the input method to indicate + the known text input state in events like preedit_string, commit_string, + and keysym. The text input can then ignore events from the input method + which are based on an outdated state (for example after a reset). + + + + Requests the text-input object to be activated (typically when the + text entry gets focus). + The seat argument is a wl_seat which maintains the focus for this + activation. The surface argument is a wl_surface assigned to the + text-input object and tracked for focus lost. The enter event + is emitted on successful activation. + + + + + + + Requests the text-input object to be deactivated (typically when the + text entry lost focus). The seat argument is a wl_seat which was used + for activation. + + + + + + Requests input panels (virtual keyboard) to show. + + + + + Requests input panels (virtual keyboard) to hide. + + + + + Should be called by an editor widget when the input state should be + reset, for example after the text was changed outside of the normal + input method flow. + + + + + Sets the plain surrounding text around the input position. Text is + UTF-8 encoded. Cursor is the byte offset within the + surrounding text. Anchor is the byte offset of the + selection anchor within the surrounding text. If there is no selected + text anchor is the same as cursor. + + + + + + + + Content hint is a bitmask to allow to modify the behavior of the text + input. + + + + + + + + + + + + + + + + + + The content purpose allows to specify the primary purpose of a text + input. + + This allows an input method to show special purpose input panels with + extra characters or to disallow some characters. + + + + + + + + + + + + + + + + + + Sets the content purpose and content hint. While the purpose is the + basic purpose of an input field, the hint flags allow to modify some + of the behavior. + + When no content type is explicitly set, a normal content purpose with + default hints (auto completion, auto correction, auto capitalization) + should be assumed. + + + + + + + + + + + + + Sets a specific language. This allows for example a virtual keyboard to + show a language specific layout. The "language" argument is a RFC-3066 + format language tag. + + It could be used for example in a word processor to indicate language of + currently edited document or in an instant message application which tracks + languages of contacts. + + + + + + + + + + + + + Notify the text-input object when it received focus. Typically in + response to an activate request. + + + + + + Notify the text-input object when it lost focus. Either in response + to a deactivate request or when the assigned surface lost focus or was + destroyed. + + + + + Transfer an array of 0-terminated modifiers names. The position in + the array is the index of the modifier as used in the modifiers + bitmask in the keysym event. + + + + + + Notify when the visibility state of the input panel changed. + + + + + + Notify when a new composing text (pre-edit) should be set around the + current cursor position. Any previously set composing text should + be removed. + + The commit text can be used to replace the preedit text on reset + (for example on unfocus). + + The text input should also handle all preedit_style and preedit_cursor + events occuring directly before preedit_string. + + + + + + + + + + + + + + + + + + Sets styling information on composing text. The style is applied for + length bytes from index relative to the beginning of the composing + text (as byte offset). Multiple styles can + be applied to a composing text by sending multiple preedit_styling + events. + + This event is handled as part of a following preedit_string event. + + + + + + + + Sets the cursor position inside the composing text (as byte + offset) relative to the start of the composing text. When index is a + negative number no cursor is shown. + + This event is handled as part of a following preedit_string event. + + + + + + Notify when text should be inserted into the editor widget. The text to + commit could be either just a single character after a key press or the + result of some composing (pre-edit). It could be also an empty text + when some text should be removed (see delete_surrounding_text) or when + the input cursor should be moved (see cursor_position). + + Any previously set composing text should be removed. + + + + + + + Notify when the cursor or anchor position should be modified. + + This event should be handled as part of a following commit_string + event. + + + + + + + Notify when the text around the current cursor position should be + deleted. + + Index is relative to the current cursor (in bytes). + Length is the length of deleted text (in bytes). + + This event should be handled as part of a following commit_string + event. + + + + + + + Notify when a key event was sent. Key events should not be used + for normal text input operations, which should be done with + commit_string, delete_surrounding_text, etc. The key event follows + the wl_keyboard key event convention. Sym is a XKB keysym, state a + wl_keyboard key_state. Modifiers are a mask for effective modifiers + (where the modifier indices are set by the modifiers_map event) + + + + + + + + + + Sets the language of the input text. The "language" argument is a RFC-3066 + format language tag. + + + + + + + + + + + + Sets the text direction of input text. + + It is mainly needed for showing input cursor on correct side of the + editor when there is no input yet done and making sure neutral + direction text is laid out properly. + + + + + + + + + A factory for text-input objects. This object is a global singleton. + + + + Creates a new text-input object. + + + + + diff --git a/protocol/wayland-test.xml b/protocol/wayland-test.xml new file mode 100644 index 00000000..2993f087 --- /dev/null +++ b/protocol/wayland-test.xml @@ -0,0 +1,55 @@ + + + + + Copyright © 2012 Intel Corporation + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/protocol/workspaces.xml b/protocol/workspaces.xml new file mode 100644 index 00000000..22f4802b --- /dev/null +++ b/protocol/workspaces.xml @@ -0,0 +1,27 @@ + + + + + An interface for managing surfaces in workspaces. + + + + + Move the given surface to the specified workspace. + + + + + + + + The current workspace state, such as current workspace and workspace + count, has changed. + + + + + + + + diff --git a/protocol/xserver.xml b/protocol/xserver.xml new file mode 100644 index 00000000..9e25f5c0 --- /dev/null +++ b/protocol/xserver.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/shared/Makefile.am b/shared/Makefile.am new file mode 100644 index 00000000..2fcff7bb --- /dev/null +++ b/shared/Makefile.am @@ -0,0 +1,32 @@ +noinst_LTLIBRARIES = libshared.la libshared-cairo.la + +libshared_la_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) + +libshared_la_SOURCES = \ + config-parser.c \ + option-parser.c \ + config-parser.h \ + os-compatibility.c \ + os-compatibility.h + +libshared_cairo_la_CFLAGS = \ + $(GCC_CFLAGS) \ + $(COMPOSITOR_CFLAGS) \ + $(PIXMAN_CFLAGS) \ + $(CAIRO_CFLAGS) \ + $(PNG_CFLAGS) \ + $(WEBP_CFLAGS) + +libshared_cairo_la_LIBADD = \ + $(PIXMAN_LIBS) \ + $(CAIRO_LIBS) \ + $(PNG_LIBS) \ + $(WEBP_LIBS) \ + $(JPEG_LIBS) + +libshared_cairo_la_SOURCES = \ + $(libshared_la_SOURCES) \ + image-loader.c \ + image-loader.h \ + cairo-util.c \ + cairo-util.h diff --git a/shared/cairo-util.c b/shared/cairo-util.c new file mode 100644 index 00000000..4305ba69 --- /dev/null +++ b/shared/cairo-util.c @@ -0,0 +1,511 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include "cairo-util.h" + +#include "image-loader.h" +#include "config-parser.h" + +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) + +void +surface_flush_device(cairo_surface_t *surface) +{ + cairo_device_t *device; + + device = cairo_surface_get_device(surface); + if (device) + cairo_device_flush(device); +} + +static int +blur_surface(cairo_surface_t *surface, int margin) +{ + int32_t width, height, stride, x, y, z, w; + uint8_t *src, *dst; + uint32_t *s, *d, a, p; + int i, j, k, size, half; + uint32_t kernel[71]; + double f; + + size = ARRAY_LENGTH(kernel); + width = cairo_image_surface_get_width(surface); + height = cairo_image_surface_get_height(surface); + stride = cairo_image_surface_get_stride(surface); + src = cairo_image_surface_get_data(surface); + + dst = malloc(height * stride); + if (dst == NULL) + return -1; + + half = size / 2; + a = 0; + for (i = 0; i < size; i++) { + f = (i - half); + kernel[i] = exp(- f * f / ARRAY_LENGTH(kernel)) * 10000; + a += kernel[i]; + } + + for (i = 0; i < height; i++) { + s = (uint32_t *) (src + i * stride); + d = (uint32_t *) (dst + i * stride); + for (j = 0; j < width; j++) { + if (margin < j && j < width - margin) { + d[j] = s[j]; + continue; + } + + x = 0; + y = 0; + z = 0; + w = 0; + for (k = 0; k < size; k++) { + if (j - half + k < 0 || j - half + k >= width) + continue; + p = s[j - half + k]; + + x += (p >> 24) * kernel[k]; + y += ((p >> 16) & 0xff) * kernel[k]; + z += ((p >> 8) & 0xff) * kernel[k]; + w += (p & 0xff) * kernel[k]; + } + d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; + } + } + + for (i = 0; i < height; i++) { + s = (uint32_t *) (dst + i * stride); + d = (uint32_t *) (src + i * stride); + for (j = 0; j < width; j++) { + if (margin <= i && i < height - margin) { + d[j] = s[j]; + continue; + } + + x = 0; + y = 0; + z = 0; + w = 0; + for (k = 0; k < size; k++) { + if (i - half + k < 0 || i - half + k >= height) + continue; + s = (uint32_t *) (dst + (i - half + k) * stride); + p = s[j]; + + x += (p >> 24) * kernel[k]; + y += ((p >> 16) & 0xff) * kernel[k]; + z += ((p >> 8) & 0xff) * kernel[k]; + w += (p & 0xff) * kernel[k]; + } + d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; + } + } + + free(dst); + cairo_surface_mark_dirty(surface); + + return 0; +} + +void +tile_mask(cairo_t *cr, cairo_surface_t *surface, + int x, int y, int width, int height, int margin, int top_margin) +{ + cairo_pattern_t *pattern; + cairo_matrix_t matrix; + int i, fx, fy, vmargin; + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + pattern = cairo_pattern_create_for_surface (surface); + cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); + + for (i = 0; i < 4; i++) { + fx = i & 1; + fy = i >> 1; + + cairo_matrix_init_translate(&matrix, + -x + fx * (128 - width), + -y + fy * (128 - height)); + cairo_pattern_set_matrix(pattern, &matrix); + + if (fy) + vmargin = margin; + else + vmargin = top_margin; + + cairo_reset_clip(cr); + cairo_rectangle(cr, + x + fx * (width - margin), + y + fy * (height - vmargin), + margin, vmargin); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + /* Top stretch */ + cairo_matrix_init_translate(&matrix, 60, 0); + cairo_matrix_scale(&matrix, 8.0 / width, 1); + cairo_matrix_translate(&matrix, -x - width / 2, -y); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + margin, y, width - 2 * margin, margin); + + cairo_reset_clip(cr); + cairo_rectangle(cr, + x + margin, + y, + width - 2 * margin, margin); + cairo_clip (cr); + cairo_mask(cr, pattern); + + /* Bottom stretch */ + cairo_matrix_translate(&matrix, 0, -height + 128); + cairo_pattern_set_matrix(pattern, &matrix); + + cairo_reset_clip(cr); + cairo_rectangle(cr, x + margin, y + height - margin, + width - 2 * margin, margin); + cairo_clip (cr); + cairo_mask(cr, pattern); + + /* Left stretch */ + cairo_matrix_init_translate(&matrix, 0, 60); + cairo_matrix_scale(&matrix, 1, 8.0 / height); + cairo_matrix_translate(&matrix, -x, -y - height / 2); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_reset_clip(cr); + cairo_rectangle(cr, x, y + margin, margin, height - 2 * margin); + cairo_clip (cr); + cairo_mask(cr, pattern); + + /* Right stretch */ + cairo_matrix_translate(&matrix, -width + 128, 0); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + width - margin, y + margin, + margin, height - 2 * margin); + cairo_reset_clip(cr); + cairo_clip (cr); + cairo_mask(cr, pattern); + + cairo_pattern_destroy(pattern); + cairo_reset_clip(cr); +} + +void +tile_source(cairo_t *cr, cairo_surface_t *surface, + int x, int y, int width, int height, int margin, int top_margin) +{ + cairo_pattern_t *pattern; + cairo_matrix_t matrix; + int i, fx, fy, vmargin; + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + pattern = cairo_pattern_create_for_surface (surface); + cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); + cairo_set_source(cr, pattern); + cairo_pattern_destroy(pattern); + + for (i = 0; i < 4; i++) { + fx = i & 1; + fy = i >> 1; + + cairo_matrix_init_translate(&matrix, + -x + fx * (128 - width), + -y + fy * (128 - height)); + cairo_pattern_set_matrix(pattern, &matrix); + + if (fy) + vmargin = margin; + else + vmargin = top_margin; + + cairo_rectangle(cr, + x + fx * (width - margin), + y + fy * (height - vmargin), + margin, vmargin); + cairo_fill(cr); + } + + /* Top stretch */ + cairo_matrix_init_translate(&matrix, 60, 0); + cairo_matrix_scale(&matrix, 8.0 / (width - 2 * margin), 1); + cairo_matrix_translate(&matrix, -x - width / 2, -y); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + margin, y, width - 2 * margin, top_margin); + cairo_fill(cr); + + /* Bottom stretch */ + cairo_matrix_translate(&matrix, 0, -height + 128); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + margin, y + height - margin, + width - 2 * margin, margin); + cairo_fill(cr); + + /* Left stretch */ + cairo_matrix_init_translate(&matrix, 0, 60); + cairo_matrix_scale(&matrix, 1, 8.0 / (height - margin - top_margin)); + cairo_matrix_translate(&matrix, -x, -y - height / 2); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x, y + top_margin, + margin, height - margin - top_margin); + cairo_fill(cr); + + /* Right stretch */ + cairo_matrix_translate(&matrix, -width + 128, 0); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + width - margin, y + top_margin, + margin, height - margin - top_margin); + cairo_fill(cr); +} + +void +rounded_rect(cairo_t *cr, int x0, int y0, int x1, int y1, int radius) +{ + cairo_move_to(cr, x0, y0 + radius); + cairo_arc(cr, x0 + radius, y0 + radius, radius, M_PI, 3 * M_PI / 2); + cairo_line_to(cr, x1 - radius, y0); + cairo_arc(cr, x1 - radius, y0 + radius, radius, 3 * M_PI / 2, 2 * M_PI); + cairo_line_to(cr, x1, y1 - radius); + cairo_arc(cr, x1 - radius, y1 - radius, radius, 0, M_PI / 2); + cairo_line_to(cr, x0 + radius, y1); + cairo_arc(cr, x0 + radius, y1 - radius, radius, M_PI / 2, M_PI); + cairo_close_path(cr); +} + +cairo_surface_t * +load_cairo_surface(const char *filename) +{ + pixman_image_t *image; + int width, height, stride; + void *data; + + image = load_image(filename); + if (image == NULL) { + return NULL; + } + + data = pixman_image_get_data(image); + width = pixman_image_get_width(image); + height = pixman_image_get_height(image); + stride = pixman_image_get_stride(image); + + return cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, + width, height, stride); +} + +struct theme * +theme_create(void) +{ + struct theme *t; + cairo_t *cr; + cairo_pattern_t *pattern; + + t = malloc(sizeof *t); + if (t == NULL) + return NULL; + + t->margin = 32; + t->width = 6; + t->titlebar_height = 27; + t->frame_radius = 3; + t->shadow = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 128, 128); + cr = cairo_create(t->shadow); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba(cr, 0, 0, 0, 1); + rounded_rect(cr, 32, 32, 96, 96, t->frame_radius); + cairo_fill(cr); + if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) + goto err_shadow; + cairo_destroy(cr); + if (blur_surface(t->shadow, 64) == -1) + goto err_shadow; + + t->active_frame = + cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 128, 128); + cr = cairo_create(t->active_frame); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + pattern = cairo_pattern_create_linear(16, 16, 16, 112); + cairo_pattern_add_color_stop_rgb(pattern, 0.0, 1.0, 1.0, 1.0); + cairo_pattern_add_color_stop_rgb(pattern, 0.2, 0.8, 0.8, 0.8); + cairo_set_source(cr, pattern); + cairo_pattern_destroy(pattern); + + rounded_rect(cr, 0, 0, 128, 128, t->frame_radius); + cairo_fill(cr); + + if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) + goto err_active_frame; + + cairo_destroy(cr); + + t->inactive_frame = + cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 128, 128); + cr = cairo_create(t->inactive_frame); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba(cr, 0.75, 0.75, 0.75, 1); + rounded_rect(cr, 0, 0, 128, 128, t->frame_radius); + cairo_fill(cr); + + if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) + goto err_inactive_frame; + + cairo_destroy(cr); + + return t; + + err_inactive_frame: + cairo_surface_destroy(t->inactive_frame); + err_active_frame: + cairo_surface_destroy(t->active_frame); + err_shadow: + cairo_surface_destroy(t->shadow); + free(t); + return NULL; +} + +void +theme_destroy(struct theme *t) +{ + cairo_surface_destroy(t->active_frame); + cairo_surface_destroy(t->inactive_frame); + cairo_surface_destroy(t->shadow); + free(t); +} + +void +theme_render_frame(struct theme *t, + cairo_t *cr, int width, int height, + const char *title, uint32_t flags) +{ + cairo_text_extents_t extents; + cairo_font_extents_t font_extents; + cairo_surface_t *source; + int x, y, margin; + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0, 0, 0, 0); + cairo_paint(cr); + + if (flags & THEME_FRAME_MAXIMIZED) + margin = 0; + else { + cairo_set_source_rgba(cr, 0, 0, 0, 0.45); + tile_mask(cr, t->shadow, + 2, 2, width + 8, height + 8, + 64, 64); + margin = t->margin; + } + + if (flags & THEME_FRAME_ACTIVE) + source = t->active_frame; + else + source = t->inactive_frame; + + tile_source(cr, source, + margin, margin, + width - margin * 2, height - margin * 2, + t->width, t->titlebar_height); + + cairo_rectangle (cr, margin + t->width, margin, + width - (margin + t->width) * 2, + t->titlebar_height - t->width); + cairo_clip(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_select_font_face(cr, "sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size(cr, 14); + cairo_text_extents(cr, title, &extents); + cairo_font_extents (cr, &font_extents); + x = (width - extents.width) / 2; + y = margin + + (t->titlebar_height - + font_extents.ascent - font_extents.descent) / 2 + + font_extents.ascent; + + if (flags & THEME_FRAME_ACTIVE) { + cairo_move_to(cr, x + 1, y + 1); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_show_text(cr, title); + cairo_move_to(cr, x, y); + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_show_text(cr, title); + } else { + cairo_move_to(cr, x, y); + cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); + cairo_show_text(cr, title); + } +} + +enum theme_location +theme_get_location(struct theme *t, int x, int y, + int width, int height, int flags) +{ + int vlocation, hlocation, location; + const int grip_size = 8; + int margin; + + margin = (flags & THEME_FRAME_MAXIMIZED) ? 0 : t->margin; + + if (x < margin) + hlocation = THEME_LOCATION_EXTERIOR; + else if (margin <= x && x < margin + grip_size) + hlocation = THEME_LOCATION_RESIZING_LEFT; + else if (x < width - margin - grip_size) + hlocation = THEME_LOCATION_INTERIOR; + else if (x < width - margin) + hlocation = THEME_LOCATION_RESIZING_RIGHT; + else + hlocation = THEME_LOCATION_EXTERIOR; + + if (y < margin) + vlocation = THEME_LOCATION_EXTERIOR; + else if (margin <= y && y < margin + grip_size) + vlocation = THEME_LOCATION_RESIZING_TOP; + else if (y < height - margin - grip_size) + vlocation = THEME_LOCATION_INTERIOR; + else if (y < height - margin) + vlocation = THEME_LOCATION_RESIZING_BOTTOM; + else + vlocation = THEME_LOCATION_EXTERIOR; + + location = vlocation | hlocation; + if (location & THEME_LOCATION_EXTERIOR) + location = THEME_LOCATION_EXTERIOR; + if (location == THEME_LOCATION_INTERIOR && + y < margin + t->titlebar_height) + location = THEME_LOCATION_TITLEBAR; + else if (location == THEME_LOCATION_INTERIOR) + location = THEME_LOCATION_CLIENT_AREA; + + return location; +} diff --git a/shared/cairo-util.h b/shared/cairo-util.h new file mode 100644 index 00000000..7b403944 --- /dev/null +++ b/shared/cairo-util.h @@ -0,0 +1,89 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef _CAIRO_UTIL_H +#define _CAIRO_UTIL_H + +#include + +void +surface_flush_device(cairo_surface_t *surface); + +void +tile_mask(cairo_t *cr, cairo_surface_t *surface, + int x, int y, int width, int height, int margin, int top_margin); + +void +tile_source(cairo_t *cr, cairo_surface_t *surface, + int x, int y, int width, int height, int margin, int top_margin); + +void +rounded_rect(cairo_t *cr, int x0, int y0, int x1, int y1, int radius); + +cairo_surface_t * +load_cairo_surface(const char *filename); + +struct theme { + cairo_surface_t *active_frame; + cairo_surface_t *inactive_frame; + cairo_surface_t *shadow; + int frame_radius; + int margin; + int width; + int titlebar_height; +}; + +struct theme * +theme_create(void); +void +theme_destroy(struct theme *t); + +enum { + THEME_FRAME_ACTIVE = 1, + THEME_FRAME_MAXIMIZED, +}; + +void +theme_render_frame(struct theme *t, + cairo_t *cr, int width, int height, + const char *title, uint32_t flags); + +enum theme_location { + THEME_LOCATION_INTERIOR = 0, + THEME_LOCATION_RESIZING_TOP = 1, + THEME_LOCATION_RESIZING_BOTTOM = 2, + THEME_LOCATION_RESIZING_LEFT = 4, + THEME_LOCATION_RESIZING_TOP_LEFT = 5, + THEME_LOCATION_RESIZING_BOTTOM_LEFT = 6, + THEME_LOCATION_RESIZING_RIGHT = 8, + THEME_LOCATION_RESIZING_TOP_RIGHT = 9, + THEME_LOCATION_RESIZING_BOTTOM_RIGHT = 10, + THEME_LOCATION_RESIZING_MASK = 15, + THEME_LOCATION_EXTERIOR = 16, + THEME_LOCATION_TITLEBAR = 17, + THEME_LOCATION_CLIENT_AREA = 18, +}; + +enum theme_location +theme_get_location(struct theme *t, int x, int y, int width, int height, int flags); + +#endif diff --git a/shared/config-parser.c b/shared/config-parser.c new file mode 100644 index 00000000..8defbbb4 --- /dev/null +++ b/shared/config-parser.c @@ -0,0 +1,434 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "config-parser.h" + +#define container_of(ptr, type, member) ({ \ + const __typeof__( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +struct weston_config_entry { + char *key; + char *value; + struct wl_list link; +}; + +struct weston_config_section { + char *name; + struct wl_list entry_list; + struct wl_list link; +}; + +struct weston_config { + struct wl_list section_list; + char path[PATH_MAX]; +}; + +static int +open_config_file(struct weston_config *c, const char *name) +{ + const char *config_dir = getenv("XDG_CONFIG_HOME"); + const char *home_dir = getenv("HOME"); + const char *config_dirs = getenv("XDG_CONFIG_DIRS"); + const char *p, *next; + int fd; + + if (name[0] == '/') { + snprintf(c->path, sizeof c->path, "%s", name); + return open(name, O_RDONLY | O_CLOEXEC); + } + + /* Precedence is given to config files in the home directory, + * and then to directories listed in XDG_CONFIG_DIRS and + * finally to the current working directory. */ + + /* $XDG_CONFIG_HOME */ + if (config_dir) { + snprintf(c->path, sizeof c->path, "%s/%s", config_dir, name); + fd = open(c->path, O_RDONLY | O_CLOEXEC); + if (fd >= 0) + return fd; + } + + /* $HOME/.config */ + if (home_dir) { + snprintf(c->path, sizeof c->path, + "%s/.config/%s", home_dir, name); + fd = open(c->path, O_RDONLY | O_CLOEXEC); + if (fd >= 0) + return fd; + } + + /* For each $XDG_CONFIG_DIRS: weston/ */ + if (!config_dirs) + config_dirs = "/etc/xdg"; /* See XDG base dir spec. */ + + for (p = config_dirs; *p != '\0'; p = next) { + next = strchrnul(p, ':'); + snprintf(c->path, sizeof c->path, + "%.*s/weston/%s", (int)(next - p), p, name); + fd = open(c->path, O_RDONLY | O_CLOEXEC); + if (fd >= 0) + return fd; + + if (*next == ':') + next++; + } + + /* Current working directory. */ + snprintf(c->path, sizeof c->path, "./%s", name); + + return open(c->path, O_RDONLY | O_CLOEXEC); +} + +static struct weston_config_entry * +config_section_get_entry(struct weston_config_section *section, + const char *key) +{ + struct weston_config_entry *e; + + if (section == NULL) + return NULL; + wl_list_for_each(e, §ion->entry_list, link) + if (strcmp(e->key, key) == 0) + return e; + + return NULL; +} + +WL_EXPORT +struct weston_config_section * +weston_config_get_section(struct weston_config *config, const char *section, + const char *key, const char *value) +{ + struct weston_config_section *s; + struct weston_config_entry *e; + + if (config == NULL) + return NULL; + wl_list_for_each(s, &config->section_list, link) { + if (strcmp(s->name, section) != 0) + continue; + if (key == NULL) + return s; + e = config_section_get_entry(s, key); + if (e && strcmp(e->value, value) == 0) + return s; + } + + return NULL; +} + +WL_EXPORT +int +weston_config_section_get_int(struct weston_config_section *section, + const char *key, + int32_t *value, int32_t default_value) +{ + struct weston_config_entry *entry; + char *end; + + entry = config_section_get_entry(section, key); + if (entry == NULL) { + *value = default_value; + errno = ENOENT; + return -1; + } + + *value = strtol(entry->value, &end, 0); + if (*end != '\0') { + *value = default_value; + errno = EINVAL; + return -1; + } + + return 0; +} + +WL_EXPORT +int +weston_config_section_get_uint(struct weston_config_section *section, + const char *key, + uint32_t *value, uint32_t default_value) +{ + struct weston_config_entry *entry; + char *end; + + entry = config_section_get_entry(section, key); + if (entry == NULL) { + *value = default_value; + errno = ENOENT; + return -1; + } + + *value = strtoul(entry->value, &end, 0); + if (*end != '\0') { + *value = default_value; + errno = EINVAL; + return -1; + } + + return 0; +} + +WL_EXPORT +int +weston_config_section_get_double(struct weston_config_section *section, + const char *key, + double *value, double default_value) +{ + struct weston_config_entry *entry; + char *end; + + entry = config_section_get_entry(section, key); + if (entry == NULL) { + *value = default_value; + errno = ENOENT; + return -1; + } + + *value = strtod(entry->value, &end); + if (*end != '\0') { + *value = default_value; + errno = EINVAL; + return -1; + } + + return 0; +} + +WL_EXPORT +int +weston_config_section_get_string(struct weston_config_section *section, + const char *key, + char **value, const char *default_value) +{ + struct weston_config_entry *entry; + + entry = config_section_get_entry(section, key); + if (entry == NULL) { + if (default_value) + *value = strdup(default_value); + else + *value = NULL; + errno = ENOENT; + return -1; + } + + *value = strdup(entry->value); + + return 0; +} + +WL_EXPORT +int +weston_config_section_get_bool(struct weston_config_section *section, + const char *key, + int *value, int default_value) +{ + struct weston_config_entry *entry; + + entry = config_section_get_entry(section, key); + if (entry == NULL) { + *value = default_value; + errno = ENOENT; + return -1; + } + + if (strcmp(entry->value, "false") == 0) + *value = 0; + else if (strcmp(entry->value, "true") == 0) + *value = 1; + else { + *value = default_value; + errno = EINVAL; + return -1; + } + + return 0; +} + +static struct weston_config_section * +config_add_section(struct weston_config *config, const char *name) +{ + struct weston_config_section *section; + + section = malloc(sizeof *section); + section->name = strdup(name); + wl_list_init(§ion->entry_list); + wl_list_insert(config->section_list.prev, §ion->link); + + return section; +} + +static struct weston_config_entry * +section_add_entry(struct weston_config_section *section, + const char *key, const char *value) +{ + struct weston_config_entry *entry; + + entry = malloc(sizeof *entry); + entry->key = strdup(key); + entry->value = strdup(value); + wl_list_insert(section->entry_list.prev, &entry->link); + + return entry; +} + +struct weston_config * +weston_config_parse(const char *name) +{ + FILE *fp; + char line[512], *p; + struct weston_config *config; + struct weston_config_section *section = NULL; + int i, fd; + + config = malloc(sizeof *config); + if (config == NULL) + return NULL; + + wl_list_init(&config->section_list); + + fd = open_config_file(config, name); + if (fd == -1) { + free(config); + return NULL; + } + + fp = fdopen(fd, "r"); + if (fp == NULL) { + free(config); + return NULL; + } + + while (fgets(line, sizeof line, fp)) { + switch (line[0]) { + case '#': + case '\n': + continue; + case '[': + p = strchr(&line[1], ']'); + if (!p || p[1] != '\n') { + fprintf(stderr, "malformed " + "section header: %s\n", line); + fclose(fp); + weston_config_destroy(config); + return NULL; + } + p[0] = '\0'; + section = config_add_section(config, &line[1]); + continue; + default: + p = strchr(line, '='); + if (!p || p == line || !section) { + fprintf(stderr, "malformed " + "config line: %s\n", line); + fclose(fp); + weston_config_destroy(config); + return NULL; + } + + p[0] = '\0'; + p++; + while (isspace(*p)) + p++; + i = strlen(p); + while (i > 0 && isspace(p[i - 1])) { + p[i - 1] = '\0'; + i--; + } + section_add_entry(section, line, p); + continue; + } + } + + fclose(fp); + + return config; +} + +const char * +weston_config_get_full_path(struct weston_config *config) +{ + return config == NULL ? NULL : config->path; +} + +int +weston_config_next_section(struct weston_config *config, + struct weston_config_section **section, + const char **name) +{ + if (config == NULL) + return 0; + + if (*section == NULL) + *section = container_of(config->section_list.next, + struct weston_config_section, link); + else + *section = container_of((*section)->link.next, + struct weston_config_section, link); + + if (&(*section)->link == &config->section_list) + return 0; + + *name = (*section)->name; + + return 1; +} + +void +weston_config_destroy(struct weston_config *config) +{ + struct weston_config_section *s, *next_s; + struct weston_config_entry *e, *next_e; + + if (config == NULL) + return; + + wl_list_for_each_safe(s, next_s, &config->section_list, link) { + wl_list_for_each_safe(e, next_e, &s->entry_list, link) { + free(e->key); + free(e->value); + free(e); + } + free(s->name); + free(s); + } + + free(config); +} diff --git a/shared/config-parser.h b/shared/config-parser.h new file mode 100644 index 00000000..745562bc --- /dev/null +++ b/shared/config-parser.h @@ -0,0 +1,114 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef CONFIGPARSER_H +#define CONFIGPARSER_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum config_key_type { + CONFIG_KEY_INTEGER, /* typeof data = int */ + CONFIG_KEY_UNSIGNED_INTEGER, /* typeof data = unsigned int */ + CONFIG_KEY_STRING, /* typeof data = char* */ + CONFIG_KEY_BOOLEAN /* typeof data = int */ +}; + +struct config_key { + const char *name; + enum config_key_type type; + void *data; +}; + +struct config_section { + const char *name; + const struct config_key *keys; + int num_keys; + void (*done)(void *data); +}; + +enum weston_option_type { + WESTON_OPTION_INTEGER, + WESTON_OPTION_UNSIGNED_INTEGER, + WESTON_OPTION_STRING, + WESTON_OPTION_BOOLEAN +}; + +struct weston_option { + enum weston_option_type type; + const char *name; + int short_name; + void *data; +}; + +int +parse_options(const struct weston_option *options, + int count, int *argc, char *argv[]); + +struct weston_config_section; +struct weston_config; + +struct weston_config_section * +weston_config_get_section(struct weston_config *config, const char *section, + const char *key, const char *value); +int +weston_config_section_get_int(struct weston_config_section *section, + const char *key, + int32_t *value, int32_t default_value); +int +weston_config_section_get_uint(struct weston_config_section *section, + const char *key, + uint32_t *value, uint32_t default_value); +int +weston_config_section_get_double(struct weston_config_section *section, + const char *key, + double *value, double default_value); +int +weston_config_section_get_string(struct weston_config_section *section, + const char *key, + char **value, + const char *default_value); +int +weston_config_section_get_bool(struct weston_config_section *section, + const char *key, + int *value, int default_value); +struct weston_config * +weston_config_parse(const char *name); + +const char * +weston_config_get_full_path(struct weston_config *config); + +void +weston_config_destroy(struct weston_config *config); + +int weston_config_next_section(struct weston_config *config, + struct weston_config_section **section, + const char **name); + + +#ifdef __cplusplus +} +#endif + +#endif /* CONFIGPARSER_H */ + diff --git a/shared/image-loader.c b/shared/image-loader.c new file mode 100644 index 00000000..35dadd3d --- /dev/null +++ b/shared/image-loader.c @@ -0,0 +1,405 @@ +/* + * Copyright © 2008-2012 Kristian Høgsberg + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "image-loader.h" + +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) + +#ifdef HAVE_WEBP +#include +#endif + +static int +stride_for_width(int width) +{ + return width * 4; +} + +static void +swizzle_row(JSAMPLE *row, JDIMENSION width) +{ + JSAMPLE *s; + uint32_t *d; + + s = row + (width - 1) * 3; + d = (uint32_t *) (row + (width - 1) * 4); + while (s >= row) { + *d = 0xff000000 | (s[0] << 16) | (s[1] << 8) | (s[2] << 0); + s -= 3; + d--; + } +} + +static void +error_exit(j_common_ptr cinfo) +{ + longjmp(cinfo->client_data, 1); +} + +static void +pixman_image_destroy_func(pixman_image_t *image, void *data) +{ + free(data); +} + +static pixman_image_t * +load_jpeg(FILE *fp) +{ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + pixman_image_t *pixman_image = NULL; + unsigned int i; + int stride, first; + JSAMPLE *data, *rows[4]; + jmp_buf env; + + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = error_exit; + cinfo.client_data = env; + if (setjmp(env)) + return NULL; + + jpeg_create_decompress(&cinfo); + + jpeg_stdio_src(&cinfo, fp); + + jpeg_read_header(&cinfo, TRUE); + + cinfo.out_color_space = JCS_RGB; + jpeg_start_decompress(&cinfo); + + stride = cinfo.output_width * 4; + data = malloc(stride * cinfo.output_height); + if (data == NULL) { + fprintf(stderr, "couldn't allocate image data\n"); + return NULL; + } + + while (cinfo.output_scanline < cinfo.output_height) { + first = cinfo.output_scanline; + for (i = 0; i < ARRAY_LENGTH(rows); i++) + rows[i] = data + (first + i) * stride; + + jpeg_read_scanlines(&cinfo, rows, ARRAY_LENGTH(rows)); + for (i = 0; first + i < cinfo.output_scanline; i++) + swizzle_row(rows[i], cinfo.output_width); + } + + jpeg_finish_decompress(&cinfo); + + jpeg_destroy_decompress(&cinfo); + + pixman_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, + cinfo.output_width, + cinfo.output_height, + (uint32_t *) data, stride); + + pixman_image_set_destroy_function(pixman_image, + pixman_image_destroy_func, data); + + return pixman_image; +} + +static inline int +multiply_alpha(int alpha, int color) +{ + int temp = (alpha * color) + 0x80; + + return ((temp + (temp >> 8)) >> 8); +} + +static void +premultiply_data(png_structp png, + png_row_infop row_info, + png_bytep data) +{ + unsigned int i; + png_bytep p; + + for (i = 0, p = data; i < row_info->rowbytes; i += 4, p += 4) { + png_byte alpha = p[3]; + uint32_t w; + + if (alpha == 0) { + w = 0; + } else { + png_byte red = p[0]; + png_byte green = p[1]; + png_byte blue = p[2]; + + if (alpha != 0xff) { + red = multiply_alpha(alpha, red); + green = multiply_alpha(alpha, green); + blue = multiply_alpha(alpha, blue); + } + w = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0); + } + + * (uint32_t *) p = w; + } +} + +static void +read_func(png_structp png, png_bytep data, png_size_t size) +{ + FILE *fp = png_get_io_ptr(png); + + if (fread(data, 1, size, fp) != size) + png_error(png, NULL); +} + +static void +png_error_callback(png_structp png, png_const_charp error_msg) +{ + longjmp (png_jmpbuf (png), 1); +} + +static pixman_image_t * +load_png(FILE *fp) +{ + png_struct *png; + png_info *info; + png_byte *data = NULL; + png_byte **row_pointers = NULL; + png_uint_32 width, height; + int depth, color_type, interlace, stride; + unsigned int i; + pixman_image_t *pixman_image = NULL; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, + png_error_callback, NULL); + if (!png) + return NULL; + + info = png_create_info_struct(png); + if (!info) { + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + if (setjmp(png_jmpbuf(png))) { + if (data) + free(data); + if (row_pointers) + free(row_pointers); + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + png_set_read_fn(png, fp, read_func); + png_read_info(png, info); + png_get_IHDR(png, info, + &width, &height, &depth, + &color_type, &interlace, NULL, NULL); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png); + + if (color_type == PNG_COLOR_TYPE_GRAY) + png_set_expand_gray_1_2_4_to_8(png); + + if (png_get_valid(png, info, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png); + + if (depth == 16) + png_set_strip_16(png); + + if (depth < 8) + png_set_packing(png); + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + if (interlace != PNG_INTERLACE_NONE) + png_set_interlace_handling(png); + + png_set_filler(png, 0xff, PNG_FILLER_AFTER); + png_set_read_user_transform_fn(png, premultiply_data); + png_read_update_info(png, info); + png_get_IHDR(png, info, + &width, &height, &depth, + &color_type, &interlace, NULL, NULL); + + + stride = stride_for_width(width); + data = malloc(stride * height); + if (!data) { + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + row_pointers = malloc(height * sizeof row_pointers[0]); + if (row_pointers == NULL) { + free(data); + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + for (i = 0; i < height; i++) + row_pointers[i] = &data[i * stride]; + + png_read_image(png, row_pointers); + png_read_end(png, info); + + free(row_pointers); + png_destroy_read_struct(&png, &info, NULL); + + pixman_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, + width, height, (uint32_t *) data, stride); + + pixman_image_set_destroy_function(pixman_image, + pixman_image_destroy_func, data); + + return pixman_image; +} + +#ifdef HAVE_WEBP + +static pixman_image_t * +load_webp(FILE *fp) +{ + WebPDecoderConfig config; + uint8_t buffer[16 * 1024]; + int len; + VP8StatusCode status; + WebPIDecoder *idec; + + if (!WebPInitDecoderConfig(&config)) { + fprintf(stderr, "Library version mismatch!\n"); + return NULL; + } + + /* webp decoding api doesn't seem to specify a min size that's + usable for GetFeatures, but 256 works... */ + len = fread(buffer, 1, 256, fp); + status = WebPGetFeatures(buffer, len, &config.input); + if (status != VP8_STATUS_OK) { + fprintf(stderr, "failed to parse webp header\n"); + WebPFreeDecBuffer(&config.output); + return NULL; + } + + config.output.colorspace = MODE_BGRA; + config.output.u.RGBA.stride = stride_for_width(config.input.width); + config.output.u.RGBA.size = + config.output.u.RGBA.stride * config.input.height; + config.output.u.RGBA.rgba = + malloc(config.output.u.RGBA.stride * config.input.height); + config.output.is_external_memory = 1; + if (!config.output.u.RGBA.rgba) { + WebPFreeDecBuffer(&config.output); + return NULL; + } + + rewind(fp); + idec = WebPINewDecoder(&config.output); + if (!idec) { + WebPFreeDecBuffer(&config.output); + return NULL; + } + + while (!feof(fp)) { + len = fread(buffer, 1, sizeof buffer, fp); + status = WebPIAppend(idec, buffer, len); + if (status != VP8_STATUS_OK) { + fprintf(stderr, "webp decode status %d\n", status); + WebPIDelete(idec); + WebPFreeDecBuffer(&config.output); + return NULL; + } + } + + WebPIDelete(idec); + WebPFreeDecBuffer(&config.output); + + return pixman_image_create_bits(PIXMAN_a8r8g8b8, + config.input.width, + config.input.height, + (uint32_t *) config.output.u.RGBA.rgba, + config.output.u.RGBA.stride); +} + +#endif + + +struct image_loader { + unsigned char header[4]; + int header_size; + pixman_image_t *(*load)(FILE *fp); +}; + +static const struct image_loader loaders[] = { + { { 0x89, 'P', 'N', 'G' }, 4, load_png }, + { { 0xff, 0xd8 }, 2, load_jpeg }, +#ifdef HAVE_WEBP + { { 'R', 'I', 'F', 'F' }, 4, load_webp } +#endif +}; + +pixman_image_t * +load_image(const char *filename) +{ + pixman_image_t *image; + unsigned char header[4]; + FILE *fp; + unsigned int i; + + fp = fopen(filename, "rb"); + if (fp == NULL) + return NULL; + + if (fread(header, sizeof header, 1, fp) != 1) { + fclose(fp); + return NULL; + } + + rewind(fp); + for (i = 0; i < ARRAY_LENGTH(loaders); i++) { + if (memcmp(header, loaders[i].header, + loaders[i].header_size) == 0) { + image = loaders[i].load(fp); + break; + } + } + + fclose(fp); + + if (i == ARRAY_LENGTH(loaders)) { + fprintf(stderr, "unrecognized file header for %s: " + "0x%02x 0x%02x 0x%02x 0x%02x\n", + filename, header[0], header[1], header[2], header[3]); + image = NULL; + } + + return image; +} diff --git a/shared/image-loader.h b/shared/image-loader.h new file mode 100644 index 00000000..445e651e --- /dev/null +++ b/shared/image-loader.h @@ -0,0 +1,31 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef _IMAGE_LOADER_H +#define _IMAGE_LOADER_H + +#include + +pixman_image_t * +load_image(const char *filename); + +#endif diff --git a/shared/matrix.c b/shared/matrix.c new file mode 100644 index 00000000..4f0b6b79 --- /dev/null +++ b/shared/matrix.c @@ -0,0 +1,273 @@ +/* + * Copyright © 2011 Intel Corporation + * Copyright © 2012 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef IN_WESTON +#include +#else +#define WL_EXPORT +#endif + +#include "matrix.h" + + +/* + * Matrices are stored in column-major order, that is the array indices are: + * 0 4 8 12 + * 1 5 9 13 + * 2 6 10 14 + * 3 7 11 15 + */ + +WL_EXPORT void +weston_matrix_init(struct weston_matrix *matrix) +{ + static const struct weston_matrix identity = { + .d = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, + .type = 0, + }; + + memcpy(matrix, &identity, sizeof identity); +} + +/* m <- n * m, that is, m is multiplied on the LEFT. */ +WL_EXPORT void +weston_matrix_multiply(struct weston_matrix *m, const struct weston_matrix *n) +{ + struct weston_matrix tmp; + const float *row, *column; + div_t d; + int i, j; + + for (i = 0; i < 16; i++) { + tmp.d[i] = 0; + d = div(i, 4); + row = m->d + d.quot * 4; + column = n->d + d.rem; + for (j = 0; j < 4; j++) + tmp.d[i] += row[j] * column[j * 4]; + } + tmp.type = m->type | n->type; + memcpy(m, &tmp, sizeof tmp); +} + +WL_EXPORT void +weston_matrix_translate(struct weston_matrix *matrix, float x, float y, float z) +{ + struct weston_matrix translate = { + .d = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1 }, + .type = WESTON_MATRIX_TRANSFORM_TRANSLATE, + }; + + weston_matrix_multiply(matrix, &translate); +} + +WL_EXPORT void +weston_matrix_scale(struct weston_matrix *matrix, float x, float y,float z) +{ + struct weston_matrix scale = { + .d = { x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1 }, + .type = WESTON_MATRIX_TRANSFORM_SCALE, + }; + + weston_matrix_multiply(matrix, &scale); +} + +WL_EXPORT void +weston_matrix_rotate_xy(struct weston_matrix *matrix, float cos, float sin) +{ + struct weston_matrix translate = { + .d = { cos, sin, 0, 0, -sin, cos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }, + .type = WESTON_MATRIX_TRANSFORM_ROTATE, + }; + + weston_matrix_multiply(matrix, &translate); +} + +/* v <- m * v */ +WL_EXPORT void +weston_matrix_transform(struct weston_matrix *matrix, struct weston_vector *v) +{ + int i, j; + struct weston_vector t; + + for (i = 0; i < 4; i++) { + t.f[i] = 0; + for (j = 0; j < 4; j++) + t.f[i] += v->f[j] * matrix->d[i + j * 4]; + } + + *v = t; +} + +static inline void +swap_rows(double *a, double *b) +{ + unsigned k; + double tmp; + + for (k = 0; k < 13; k += 4) { + tmp = a[k]; + a[k] = b[k]; + b[k] = tmp; + } +} + +static inline void +swap_unsigned(unsigned *a, unsigned *b) +{ + unsigned tmp; + + tmp = *a; + *a = *b; + *b = tmp; +} + +static inline unsigned +find_pivot(double *column, unsigned k) +{ + unsigned p = k; + for (++k; k < 4; ++k) + if (fabs(column[p]) < fabs(column[k])) + p = k; + + return p; +} + +/* + * reference: Gene H. Golub and Charles F. van Loan. Matrix computations. + * 3rd ed. The Johns Hopkins University Press. 1996. + * LU decomposition, forward and back substitution: Chapter 3. + */ + +MATRIX_TEST_EXPORT inline int +matrix_invert(double *A, unsigned *p, const struct weston_matrix *matrix) +{ + unsigned i, j, k; + unsigned pivot; + double pv; + + for (i = 0; i < 4; ++i) + p[i] = i; + for (i = 16; i--; ) + A[i] = matrix->d[i]; + + /* LU decomposition with partial pivoting */ + for (k = 0; k < 4; ++k) { + pivot = find_pivot(&A[k * 4], k); + if (pivot != k) { + swap_unsigned(&p[k], &p[pivot]); + swap_rows(&A[k], &A[pivot]); + } + + pv = A[k * 4 + k]; + if (fabs(pv) < 1e-9) + return -1; /* zero pivot, not invertible */ + + for (i = k + 1; i < 4; ++i) { + A[i + k * 4] /= pv; + + for (j = k + 1; j < 4; ++j) + A[i + j * 4] -= A[i + k * 4] * A[k + j * 4]; + } + } + + return 0; +} + +MATRIX_TEST_EXPORT inline void +inverse_transform(const double *LU, const unsigned *p, float *v) +{ + /* Solve A * x = v, when we have P * A = L * U. + * P * A * x = P * v => L * U * x = P * v + * Let U * x = b, then L * b = P * v. + */ + double b[4]; + unsigned j; + + /* Forward substitution, column version, solves L * b = P * v */ + /* The diagonal of L is all ones, and not explicitly stored. */ + b[0] = v[p[0]]; + b[1] = (double)v[p[1]] - b[0] * LU[1 + 0 * 4]; + b[2] = (double)v[p[2]] - b[0] * LU[2 + 0 * 4]; + b[3] = (double)v[p[3]] - b[0] * LU[3 + 0 * 4]; + b[2] -= b[1] * LU[2 + 1 * 4]; + b[3] -= b[1] * LU[3 + 1 * 4]; + b[3] -= b[2] * LU[3 + 2 * 4]; + + /* backward substitution, column version, solves U * y = b */ +#if 1 + /* hand-unrolled, 25% faster for whole function */ + b[3] /= LU[3 + 3 * 4]; + b[0] -= b[3] * LU[0 + 3 * 4]; + b[1] -= b[3] * LU[1 + 3 * 4]; + b[2] -= b[3] * LU[2 + 3 * 4]; + + b[2] /= LU[2 + 2 * 4]; + b[0] -= b[2] * LU[0 + 2 * 4]; + b[1] -= b[2] * LU[1 + 2 * 4]; + + b[1] /= LU[1 + 1 * 4]; + b[0] -= b[1] * LU[0 + 1 * 4]; + + b[0] /= LU[0 + 0 * 4]; +#else + for (j = 3; j > 0; --j) { + unsigned k; + b[j] /= LU[j + j * 4]; + for (k = 0; k < j; ++k) + b[k] -= b[j] * LU[k + j * 4]; + } + + b[0] /= LU[0 + 0 * 4]; +#endif + + /* the result */ + for (j = 0; j < 4; ++j) + v[j] = b[j]; +} + +WL_EXPORT int +weston_matrix_invert(struct weston_matrix *inverse, + const struct weston_matrix *matrix) +{ + double LU[16]; /* column-major */ + unsigned perm[4]; /* permutation */ + unsigned c; + + if (matrix_invert(LU, perm, matrix) < 0) + return -1; + + weston_matrix_init(inverse); + for (c = 0; c < 4; ++c) + inverse_transform(LU, perm, &inverse->d[c * 4]); + inverse->type = matrix->type; + + return 0; +} diff --git a/shared/matrix.h b/shared/matrix.h new file mode 100644 index 00000000..e5cf636b --- /dev/null +++ b/shared/matrix.h @@ -0,0 +1,82 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2012 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef WESTON_MATRIX_H +#define WESTON_MATRIX_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum weston_matrix_transform_type { + WESTON_MATRIX_TRANSFORM_TRANSLATE = (1 << 0), + WESTON_MATRIX_TRANSFORM_SCALE = (1 << 1), + WESTON_MATRIX_TRANSFORM_ROTATE = (1 << 2), + WESTON_MATRIX_TRANSFORM_OTHER = (1 << 3), +}; + +struct weston_matrix { + float d[16]; + unsigned int type; +}; + +struct weston_vector { + float f[4]; +}; + +void +weston_matrix_init(struct weston_matrix *matrix); +void +weston_matrix_multiply(struct weston_matrix *m, const struct weston_matrix *n); +void +weston_matrix_scale(struct weston_matrix *matrix, float x, float y, float z); +void +weston_matrix_translate(struct weston_matrix *matrix, + float x, float y, float z); +void +weston_matrix_rotate_xy(struct weston_matrix *matrix, float cos, float sin); +void +weston_matrix_transform(struct weston_matrix *matrix, struct weston_vector *v); + +int +weston_matrix_invert(struct weston_matrix *inverse, + const struct weston_matrix *matrix); + +#ifdef UNIT_TEST +# define MATRIX_TEST_EXPORT WL_EXPORT + +int +matrix_invert(double *A, unsigned *p, const struct weston_matrix *matrix); + +void +inverse_transform(const double *LU, const unsigned *p, float *v); + +#else +# define MATRIX_TEST_EXPORT static +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_MATRIX_H */ diff --git a/shared/option-parser.c b/shared/option-parser.c new file mode 100644 index 00000000..c00349a8 --- /dev/null +++ b/shared/option-parser.c @@ -0,0 +1,85 @@ +/* + * Copyright © 2012 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "config-parser.h" + +static void +handle_option(const struct weston_option *option, char *value) +{ + switch (option->type) { + case WESTON_OPTION_INTEGER: + * (int32_t *) option->data = strtol(value, NULL, 0); + return; + case WESTON_OPTION_UNSIGNED_INTEGER: + * (uint32_t *) option->data = strtoul(value, NULL, 0); + return; + case WESTON_OPTION_STRING: + * (char **) option->data = strdup(value); + return; + case WESTON_OPTION_BOOLEAN: + * (int32_t *) option->data = 1; + return; + default: + assert(0); + } +} + +int +parse_options(const struct weston_option *options, + int count, int *argc, char *argv[]) +{ + int i, j, k, len = 0; + + for (i = 1, j = 1; i < *argc; i++) { + for (k = 0; k < count; k++) { + if (options[k].name) + len = strlen(options[k].name); + if (options[k].name && + argv[i][0] == '-' && + argv[i][1] == '-' && + strncmp(options[k].name, &argv[i][2], len) == 0 && + (argv[i][len + 2] == '=' || argv[i][len + 2] == '\0')) { + handle_option(&options[k], &argv[i][len + 3]); + break; + } else if (options[k].short_name && + argv[i][0] == '-' && + options[k].short_name == argv[i][1]) { + handle_option(&options[k], &argv[i][2]); + break; + } + } + if (k == count) + argv[j++] = argv[i]; + } + argv[j] = NULL; + *argc = j; + + return j; +} diff --git a/shared/os-compatibility.c b/shared/os-compatibility.c new file mode 100644 index 00000000..4f96dd4c --- /dev/null +++ b/shared/os-compatibility.c @@ -0,0 +1,180 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "os-compatibility.h" + +static int +set_cloexec_or_close(int fd) +{ + long flags; + + if (fd == -1) + return -1; + + flags = fcntl(fd, F_GETFD); + if (flags == -1) + goto err; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + goto err; + + return fd; + +err: + close(fd); + return -1; +} + +int +os_socketpair_cloexec(int domain, int type, int protocol, int *sv) +{ + int ret; + +#ifdef SOCK_CLOEXEC + ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv); + if (ret == 0 || errno != EINVAL) + return ret; +#endif + + ret = socketpair(domain, type, protocol, sv); + if (ret < 0) + return ret; + + sv[0] = set_cloexec_or_close(sv[0]); + sv[1] = set_cloexec_or_close(sv[1]); + + if (sv[0] != -1 && sv[1] != -1) + return 0; + + close(sv[0]); + close(sv[1]); + return -1; +} + +int +os_epoll_create_cloexec(void) +{ + int fd; + +#ifdef EPOLL_CLOEXEC + fd = epoll_create1(EPOLL_CLOEXEC); + if (fd >= 0) + return fd; + if (errno != EINVAL) + return -1; +#endif + + fd = epoll_create(1); + return set_cloexec_or_close(fd); +} + +static int +create_tmpfile_cloexec(char *tmpname) +{ + int fd; + +#ifdef HAVE_MKOSTEMP + fd = mkostemp(tmpname, O_CLOEXEC); + if (fd >= 0) + unlink(tmpname); +#else + fd = mkstemp(tmpname); + if (fd >= 0) { + fd = set_cloexec_or_close(fd); + unlink(tmpname); + } +#endif + + return fd; +} + +/* + * Create a new, unique, anonymous file of the given size, and + * return the file descriptor for it. The file descriptor is set + * CLOEXEC. The file is immediately suitable for mmap()'ing + * the given size at offset zero. + * + * The file should not have a permanent backing store like a disk, + * but may have if XDG_RUNTIME_DIR is not properly implemented in OS. + * + * The file name is deleted from the file system. + * + * The file is suitable for buffer sharing between processes by + * transmitting the file descriptor over Unix sockets using the + * SCM_RIGHTS methods. + */ +int +os_create_anonymous_file(off_t size) +{ + static const char template[] = "/weston-shared-XXXXXX"; + const char *path; + char *name; + int fd; + + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } + + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; + + strcpy(name, path); + strcat(name, template); + + fd = create_tmpfile_cloexec(name); + + free(name); + + if (fd < 0) + return -1; + + if (ftruncate(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} + +#ifndef HAVE_STRCHRNUL +char * +strchrnul(const char *s, int c) +{ + while (*s && *s != c) + s++; + return (char *)s; +} +#endif diff --git a/shared/os-compatibility.h b/shared/os-compatibility.h new file mode 100644 index 00000000..c1edcfbd --- /dev/null +++ b/shared/os-compatibility.h @@ -0,0 +1,54 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef OS_COMPATIBILITY_H +#define OS_COMPATIBILITY_H + +#include + +#include "../config.h" + +#ifdef HAVE_EXECINFO_H +#include +#else +static inline int +backtrace(void **buffer, int size) +{ + return 0; +} +#endif + +int +os_socketpair_cloexec(int domain, int type, int protocol, int *sv); + +int +os_epoll_create_cloexec(void); + +int +os_create_anonymous_file(off_t size); + +#ifndef HAVE_STRCHRNUL +char * +strchrnul(const char *s, int c); +#endif + +#endif /* OS_COMPATIBILITY_H */ diff --git a/shared/zalloc.h b/shared/zalloc.h new file mode 100644 index 00000000..29da1190 --- /dev/null +++ b/shared/zalloc.h @@ -0,0 +1,42 @@ +/* + * Copyright © 2013 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef WESTON_ZALLOC_H +#define WESTON_ZALLOC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +static inline void * +zalloc(size_t size) +{ + return calloc(1, size); +} + +#ifdef __cplusplus +} +#endif + +#endif /* WESTON_ZALLOC_H */ diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..539150d4 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,24 @@ +git-version.h +version.h +weston +weston-launch +screenshooter-protocol.c +screenshooter-server-protocol.h +spring-tool +text-cursor-position-protocol.c +text-cursor-position-server-protocol.h +tablet-shell-protocol.c +tablet-shell-server-protocol.h +xserver-protocol.c +xserver-server-protocol.h +desktop-shell-protocol.c +desktop-shell-server-protocol.h +text-protocol.c +text-server-protocol.h +workspaces-protocol.c +workspaces-server-protocol.h +input-method-protocol.c +input-method-server-protocol.h +subsurface-server-protocol.h +subsurface-protocol.c + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..749c074a --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,326 @@ +bin_PROGRAMS = weston \ + $(weston_launch) + +AM_CPPFLAGS = \ + -I$(top_srcdir)/shared \ + -DDATADIR='"$(datadir)"' \ + -DMODULEDIR='"$(moduledir)"' \ + -DLIBEXECDIR='"$(libexecdir)"' \ + -DIN_WESTON + +weston_LDFLAGS = -export-dynamic +weston_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) $(LIBUNWIND_CFLAGS) +weston_LDADD = $(COMPOSITOR_LIBS) $(LIBUNWIND_LIBS) \ + $(DLOPEN_LIBS) -lm ../shared/libshared.la + +weston_SOURCES = \ + git-version.h \ + log.c \ + compositor.c \ + compositor.h \ + input.c \ + data-device.c \ + filter.c \ + filter.h \ + screenshooter.c \ + screenshooter-protocol.c \ + screenshooter-server-protocol.h \ + clipboard.c \ + text-cursor-position-protocol.c \ + text-cursor-position-server-protocol.h \ + zoom.c \ + text-backend.c \ + text-protocol.c \ + text-server-protocol.h \ + input-method-protocol.c \ + input-method-server-protocol.h \ + workspaces-protocol.c \ + workspaces-server-protocol.h \ + subsurface-protocol.c \ + subsurface-server-protocol.h \ + bindings.c \ + animation.c \ + gl-renderer.h \ + noop-renderer.c \ + pixman-renderer.c \ + pixman-renderer.h \ + ../shared/matrix.c \ + ../shared/matrix.h \ + ../shared/zalloc.h \ + weston-launch.h \ + weston-egl-ext.h + +if ENABLE_EGL +weston_SOURCES += \ + gl-renderer.c \ + vertex-clipping.c \ + vertex-clipping.h +endif + +git-version.h : .FORCE + $(AM_V_GEN)(echo "#define BUILD_ID \"$(shell git --git-dir=$(top_srcdir)/.git describe --always --dirty) $(shell git --git-dir=$(top_srcdir)/.git log -1 --format='%s (%ci)')\"" > $@-new; \ + cmp -s $@ $@-new || cp $@-new $@; \ + rm $@-new) + +.FORCE : + +if ENABLE_XWAYLAND +SUBDIRS = xwayland +endif + +DIST_SUBDIRS = xwayland + + +if BUILD_WESTON_LAUNCH +weston_launch = weston-launch +weston_launch_SOURCES = weston-launch.c weston-launch.h +weston_launch_CFLAGS= $(GCC_CFLAGS) +weston_launch_CPPFLAGS = $(WESTON_LAUNCH_CFLAGS) $(SYSTEMD_LOGIN_CFLAGS) \ + -DBINDIR='"$(bindir)"' +weston_launch_LDADD = $(WESTON_LAUNCH_LIBS) $(SYSTEMD_LOGIN_LIBS) + +if ENABLE_SETUID_INSTALL +install-exec-hook: + chown root $(DESTDIR)$(bindir)/weston-launch + chmod u+s $(DESTDIR)$(bindir)/weston-launch +endif + +endif # BUILD_WESTON_LAUNCH + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = weston.pc + +westonincludedir = $(includedir)/weston +westoninclude_HEADERS = \ + version.h \ + compositor.h \ + ../shared/matrix.h \ + ../shared/config-parser.h \ + ../shared/zalloc.h + +moduledir = $(libdir)/weston +module_LTLIBRARIES = \ + $(desktop_shell) \ + $(tablet_shell) \ + $(cms_static) \ + $(cms_colord) \ + $(x11_backend) \ + $(drm_backend) \ + $(wayland_backend) \ + $(headless_backend) \ + $(fbdev_backend) \ + $(rdp_backend) + +noinst_LTLIBRARIES = + +if INSTALL_RPI_COMPOSITOR +module_LTLIBRARIES += $(rpi_backend) +else +noinst_LTLIBRARIES += $(rpi_backend) +endif + +if ENABLE_X11_COMPOSITOR +x11_backend = x11-backend.la +x11_backend_la_LDFLAGS = -module -avoid-version +x11_backend_la_LIBADD = $(COMPOSITOR_LIBS) $(X11_COMPOSITOR_LIBS) \ + ../shared/libshared-cairo.la +x11_backend_la_CFLAGS = \ + $(COMPOSITOR_CFLAGS) \ + $(PIXMAN_CFLAGS) \ + $(CAIRO_CFLAGS) \ + $(X11_COMPOSITOR_CFLAGS) \ + $(GCC_CFLAGS) +x11_backend_la_SOURCES = compositor-x11.c +endif + +if ENABLE_DRM_COMPOSITOR +drm_backend = drm-backend.la +drm_backend_la_LDFLAGS = -module -avoid-version +drm_backend_la_LIBADD = $(COMPOSITOR_LIBS) $(DRM_COMPOSITOR_LIBS) \ + ../shared/libshared.la -lrt +drm_backend_la_CFLAGS = \ + $(COMPOSITOR_CFLAGS) \ + $(DRM_COMPOSITOR_CFLAGS) \ + $(GCC_CFLAGS) +drm_backend_la_SOURCES = \ + compositor-drm.c \ + udev-seat.c \ + udev-seat.h \ + evdev.c \ + evdev.h \ + evdev-touchpad.c \ + launcher-util.c \ + launcher-util.h \ + libbacklight.c \ + libbacklight.h + +if ENABLE_VAAPI_RECORDER +drm_backend_la_SOURCES += vaapi-recorder.c vaapi-recorder.h +drm_backend_la_LIBADD += $(LIBVA_LIBS) +drm_backend_la_CFLAGS += $(LIBVA_CFLAGS) +endif +endif + +if ENABLE_WAYLAND_COMPOSITOR +wayland_backend = wayland-backend.la +wayland_backend_la_LDFLAGS = -module -avoid-version +wayland_backend_la_LIBADD = $(COMPOSITOR_LIBS) $(WAYLAND_COMPOSITOR_LIBS) \ + ../shared/libshared-cairo.la +wayland_backend_la_CFLAGS = \ + $(COMPOSITOR_CFLAGS) \ + $(PIXMAN_CFLAGS) \ + $(CAIRO_CFLAGS) \ + $(WAYLAND_COMPOSITOR_CFLAGS) \ + $(GCC_CFLAGS) +wayland_backend_la_SOURCES = compositor-wayland.c +endif + +if ENABLE_RPI_COMPOSITOR +rpi_backend = rpi-backend.la +rpi_backend_la_LDFLAGS = -module -avoid-version +rpi_backend_la_LIBADD = $(COMPOSITOR_LIBS) \ + $(RPI_COMPOSITOR_LIBS) \ + $(RPI_BCM_HOST_LIBS) \ + ../shared/libshared.la +rpi_backend_la_CFLAGS = \ + $(GCC_CFLAGS) \ + $(COMPOSITOR_CFLAGS) \ + $(RPI_COMPOSITOR_CFLAGS) \ + $(RPI_BCM_HOST_CFLAGS) +rpi_backend_la_SOURCES = \ + compositor-rpi.c \ + rpi-renderer.c \ + rpi-renderer.h \ + rpi-bcm-stubs.h \ + launcher-util.c \ + launcher-util.h \ + evdev.c \ + evdev.h \ + evdev-touchpad.c +endif + +if ENABLE_HEADLESS_COMPOSITOR +headless_backend = headless-backend.la +headless_backend_la_LDFLAGS = -module -avoid-version +headless_backend_la_LIBADD = $(COMPOSITOR_LIBS) \ + ../shared/libshared.la +headless_backend_la_CFLAGS = \ + $(COMPOSITOR_CFLAGS) \ + $(GCC_CFLAGS) +headless_backend_la_SOURCES = compositor-headless.c +endif + +if ENABLE_FBDEV_COMPOSITOR +fbdev_backend = fbdev-backend.la +fbdev_backend_la_LDFLAGS = -module -avoid-version +fbdev_backend_la_LIBADD = \ + $(COMPOSITOR_LIBS) \ + $(FBDEV_COMPOSITOR_LIBS) \ + ../shared/libshared.la +fbdev_backend_la_CFLAGS = \ + $(COMPOSITOR_CFLAGS) \ + $(FBDEV_COMPOSITOR_CFLAGS) \ + $(PIXMAN_CFLAGS) \ + $(GCC_CFLAGS) +fbdev_backend_la_SOURCES = \ + compositor-fbdev.c \ + udev-seat.c \ + udev-seat.h \ + evdev.c \ + evdev.h \ + evdev-touchpad.c \ + launcher-util.c \ + launcher-util.h +endif + +if ENABLE_RDP_COMPOSITOR +rdp_backend = rdp-backend.la +rdp_backend_la_LDFLAGS = -module -avoid-version +rdp_backend_la_LIBADD = $(COMPOSITOR_LIBS) \ + $(RDP_COMPOSITOR_LIBS) \ + ../shared/libshared.la +rdp_backend_la_CFLAGS = \ + $(COMPOSITOR_CFLAGS) \ + $(RDP_COMPOSITOR_CFLAGS) \ + $(GCC_CFLAGS) +rdp_backend_la_SOURCES = compositor-rdp.c +endif + +if ENABLE_DESKTOP_SHELL +desktop_shell = desktop-shell.la +desktop_shell_la_LDFLAGS = -module -avoid-version +desktop_shell_la_LIBADD = $(COMPOSITOR_LIBS) \ + ../shared/libshared.la +desktop_shell_la_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) +desktop_shell_la_SOURCES = \ + shell.c \ + desktop-shell-protocol.c \ + desktop-shell-server-protocol.h +endif + +if ENABLE_TABLET_SHELL +tablet_shell = tablet-shell.la +tablet_shell_la_LDFLAGS = -module -avoid-version +tablet_shell_la_LIBADD = $(COMPOSITOR_LIBS) +tablet_shell_la_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) +tablet_shell_la_SOURCES = \ + tablet-shell.c \ + tablet-shell-protocol.c \ + tablet-shell-server-protocol.h +endif + +if HAVE_LCMS +cms_static = cms-static.la +cms_static_la_LDFLAGS = -module -avoid-version +cms_static_la_LIBADD = $(COMPOSITOR_LIBS) $(LCMS_LIBS) ../shared/libshared.la +cms_static_la_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) $(LCMS_CFLAGS) +cms_static_la_SOURCES = \ + cms-static.c \ + cms-helper.c \ + cms-helper.h +if ENABLE_COLORD +cms_colord = cms-colord.la +cms_colord_la_LDFLAGS = -module -avoid-version +cms_colord_la_LIBADD = $(COMPOSITOR_LIBS) $(COLORD_LIBS) +cms_colord_la_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) $(COLORD_CFLAGS) +cms_colord_la_SOURCES = \ + cms-colord.c \ + cms-helper.c \ + cms-helper.h +endif +endif + +noinst_PROGRAMS = spring-tool + +spring_tool_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) +spring_tool_LDADD = $(COMPOSITOR_LIBS) -lm +spring_tool_SOURCES = \ + spring-tool.c \ + animation.c \ + ../shared/matrix.c \ + ../shared/matrix.h \ + compositor.h + +BUILT_SOURCES = \ + screenshooter-server-protocol.h \ + screenshooter-protocol.c \ + text-cursor-position-server-protocol.h \ + text-cursor-position-protocol.c \ + tablet-shell-protocol.c \ + tablet-shell-server-protocol.h \ + desktop-shell-protocol.c \ + desktop-shell-server-protocol.h \ + text-protocol.c \ + text-server-protocol.h \ + input-method-protocol.c \ + input-method-server-protocol.h \ + workspaces-server-protocol.h \ + workspaces-protocol.c \ + subsurface-server-protocol.h \ + subsurface-protocol.c \ + git-version.h + +CLEANFILES = $(BUILT_SOURCES) + +wayland_protocoldir = $(top_srcdir)/protocol +include $(top_srcdir)/wayland-scanner.mk diff --git a/src/animation.c b/src/animation.c new file mode 100644 index 00000000..2c47ae7e --- /dev/null +++ b/src/animation.c @@ -0,0 +1,336 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include + +#include "compositor.h" + +WL_EXPORT void +weston_spring_init(struct weston_spring *spring, + double k, double current, double target) +{ + spring->k = k; + spring->friction = 400.0; + spring->current = current; + spring->previous = current; + spring->target = target; + spring->clip = WESTON_SPRING_OVERSHOOT; + spring->min = 0.0; + spring->max = 1.0; +} + +WL_EXPORT void +weston_spring_update(struct weston_spring *spring, uint32_t msec) +{ + double force, v, current, step; + + /* Limit the number of executions of the loop below by ensuring that + * the timestamp for last update of the spring is no more than 1s ago. + * This handles the case where time moves backwards or forwards in + * large jumps. + */ + if (msec - spring->timestamp > 1000) { + weston_log("unexpectedly large timestamp jump (from %u to %u)\n", + spring->timestamp, msec); + spring->timestamp = msec - 1000; + } + + step = 0.01; + while (4 < msec - spring->timestamp) { + current = spring->current; + v = current - spring->previous; + force = spring->k * (spring->target - current) / 10.0 + + (spring->previous - current) - v * spring->friction; + + spring->current = + current + (current - spring->previous) + + force * step * step; + spring->previous = current; + + switch (spring->clip) { + case WESTON_SPRING_OVERSHOOT: + break; + + case WESTON_SPRING_CLAMP: + if (spring->current > spring->max) { + spring->current = spring->max; + spring->previous = spring->max; + } else if (spring->current < 0.0) { + spring->current = spring->min; + spring->previous = spring->min; + } + break; + + case WESTON_SPRING_BOUNCE: + if (spring->current > spring->max) { + spring->current = + 2 * spring->max - spring->current; + spring->previous = + 2 * spring->max - spring->previous; + } else if (spring->current < spring->min) { + spring->current = + 2 * spring->min - spring->current; + spring->previous = + 2 * spring->min - spring->previous; + } + break; + } + + spring->timestamp += 4; + } +} + +WL_EXPORT int +weston_spring_done(struct weston_spring *spring) +{ + return fabs(spring->previous - spring->target) < 0.002 && + fabs(spring->current - spring->target) < 0.002; +} + +typedef void (*weston_surface_animation_frame_func_t)(struct weston_surface_animation *animation); + +struct weston_surface_animation { + struct weston_surface *surface; + struct weston_animation animation; + struct weston_spring spring; + struct weston_transform transform; + struct wl_listener listener; + float start, stop; + weston_surface_animation_frame_func_t frame; + weston_surface_animation_frame_func_t reset; + weston_surface_animation_done_func_t done; + void *data; +}; + +static void +weston_surface_animation_destroy(struct weston_surface_animation *animation) +{ + wl_list_remove(&animation->animation.link); + wl_list_remove(&animation->listener.link); + wl_list_remove(&animation->transform.link); + if (animation->reset) + animation->reset(animation); + weston_surface_geometry_dirty(animation->surface); + if (animation->done) + animation->done(animation, animation->data); + free(animation); +} + +static void +handle_animation_surface_destroy(struct wl_listener *listener, void *data) +{ + struct weston_surface_animation *animation = + container_of(listener, + struct weston_surface_animation, listener); + + weston_surface_animation_destroy(animation); +} + +static void +weston_surface_animation_frame(struct weston_animation *base, + struct weston_output *output, uint32_t msecs) +{ + struct weston_surface_animation *animation = + container_of(base, + struct weston_surface_animation, animation); + + if (base->frame_counter <= 1) + animation->spring.timestamp = msecs; + + weston_spring_update(&animation->spring, msecs); + + if (weston_spring_done(&animation->spring)) { + weston_compositor_schedule_repaint(animation->surface->compositor); + weston_surface_animation_destroy(animation); + return; + } + + if (animation->frame) + animation->frame(animation); + + weston_surface_geometry_dirty(animation->surface); + weston_compositor_schedule_repaint(animation->surface->compositor); +} + +static struct weston_surface_animation * +weston_surface_animation_run(struct weston_surface *surface, + float start, float stop, + weston_surface_animation_frame_func_t frame, + weston_surface_animation_frame_func_t reset, + weston_surface_animation_done_func_t done, + void *data) +{ + struct weston_surface_animation *animation; + + animation = malloc(sizeof *animation); + if (!animation) + return NULL; + + animation->surface = surface; + animation->frame = frame; + animation->reset = reset; + animation->done = done; + animation->data = data; + animation->start = start; + animation->stop = stop; + weston_matrix_init(&animation->transform.matrix); + wl_list_insert(&surface->geometry.transformation_list, + &animation->transform.link); + weston_spring_init(&animation->spring, 200.0, 0.0, 1.0); + animation->spring.friction = 700; + animation->animation.frame_counter = 0; + animation->animation.frame = weston_surface_animation_frame; + weston_surface_animation_frame(&animation->animation, NULL, 0); + + animation->listener.notify = handle_animation_surface_destroy; + wl_signal_add(&surface->destroy_signal, &animation->listener); + + wl_list_insert(&surface->output->animation_list, + &animation->animation.link); + + return animation; +} + +static void +reset_alpha(struct weston_surface_animation *animation) +{ + struct weston_surface *surface = animation->surface; + + surface->alpha = animation->stop; +} + +static void +zoom_frame(struct weston_surface_animation *animation) +{ + struct weston_surface *es = animation->surface; + float scale; + + scale = animation->start + + (animation->stop - animation->start) * + animation->spring.current; + weston_matrix_init(&animation->transform.matrix); + weston_matrix_translate(&animation->transform.matrix, + -0.5f * es->geometry.width, + -0.5f * es->geometry.height, 0); + weston_matrix_scale(&animation->transform.matrix, scale, scale, scale); + weston_matrix_translate(&animation->transform.matrix, + 0.5f * es->geometry.width, + 0.5f * es->geometry.height, 0); + + es->alpha = animation->spring.current; + if (es->alpha > 1.0) + es->alpha = 1.0; +} + +WL_EXPORT struct weston_surface_animation * +weston_zoom_run(struct weston_surface *surface, float start, float stop, + weston_surface_animation_done_func_t done, void *data) +{ + struct weston_surface_animation *zoom; + + zoom = weston_surface_animation_run(surface, start, stop, + zoom_frame, reset_alpha, + done, data); + + weston_spring_init(&zoom->spring, 300.0, start, stop); + zoom->spring.friction = 1400; + zoom->spring.previous = start - (stop - start) * 0.03; + + return zoom; +} + +static void +fade_frame(struct weston_surface_animation *animation) +{ + if (animation->spring.current > 0.999) + animation->surface->alpha = 1; + else if (animation->spring.current < 0.001 ) + animation->surface->alpha = 0; + else + animation->surface->alpha = animation->spring.current; +} + +WL_EXPORT struct weston_surface_animation * +weston_fade_run(struct weston_surface *surface, + float start, float end, float k, + weston_surface_animation_done_func_t done, void *data) +{ + struct weston_surface_animation *fade; + + fade = weston_surface_animation_run(surface, 0, end, + fade_frame, reset_alpha, + done, data); + + weston_spring_init(&fade->spring, k, start, end); + + fade->spring.friction = 1400; + fade->spring.previous = -(end - start) * 0.03; + + surface->alpha = start; + + return fade; +} + +WL_EXPORT void +weston_fade_update(struct weston_surface_animation *fade, float target) +{ + fade->spring.target = target; +} + +static void +slide_frame(struct weston_surface_animation *animation) +{ + float scale; + + scale = animation->start + + (animation->stop - animation->start) * + animation->spring.current; + weston_matrix_init(&animation->transform.matrix); + weston_matrix_translate(&animation->transform.matrix, 0, scale, 0); +} + +WL_EXPORT struct weston_surface_animation * +weston_slide_run(struct weston_surface *surface, float start, float stop, + weston_surface_animation_done_func_t done, void *data) +{ + struct weston_surface_animation *animation; + + animation = weston_surface_animation_run(surface, start, stop, + slide_frame, NULL, done, + data); + if (!animation) + return NULL; + + animation->spring.friction = 600; + animation->spring.k = 400; + animation->spring.clip = WESTON_SPRING_BOUNCE; + + return animation; +} diff --git a/src/bindings.c b/src/bindings.c new file mode 100644 index 00000000..7cbded92 --- /dev/null +++ b/src/bindings.c @@ -0,0 +1,342 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include + +#include "compositor.h" + +struct weston_binding { + uint32_t key; + uint32_t button; + uint32_t axis; + uint32_t modifier; + void *handler; + void *data; + struct wl_list link; +}; + +static struct weston_binding * +weston_compositor_add_binding(struct weston_compositor *compositor, + uint32_t key, uint32_t button, uint32_t axis, + uint32_t modifier, void *handler, void *data) +{ + struct weston_binding *binding; + + binding = malloc(sizeof *binding); + if (binding == NULL) + return NULL; + + binding->key = key; + binding->button = button; + binding->axis = axis; + binding->modifier = modifier; + binding->handler = handler; + binding->data = data; + + return binding; +} + +WL_EXPORT struct weston_binding * +weston_compositor_add_key_binding(struct weston_compositor *compositor, + uint32_t key, uint32_t modifier, + weston_key_binding_handler_t handler, + void *data) +{ + struct weston_binding *binding; + + binding = weston_compositor_add_binding(compositor, key, 0, 0, + modifier, handler, data); + if (binding == NULL) + return NULL; + + wl_list_insert(compositor->key_binding_list.prev, &binding->link); + + return binding; +} + +WL_EXPORT struct weston_binding * +weston_compositor_add_button_binding(struct weston_compositor *compositor, + uint32_t button, uint32_t modifier, + weston_button_binding_handler_t handler, + void *data) +{ + struct weston_binding *binding; + + binding = weston_compositor_add_binding(compositor, 0, button, 0, + modifier, handler, data); + if (binding == NULL) + return NULL; + + wl_list_insert(compositor->button_binding_list.prev, &binding->link); + + return binding; +} + +WL_EXPORT struct weston_binding * +weston_compositor_add_touch_binding(struct weston_compositor *compositor, + uint32_t modifier, + weston_touch_binding_handler_t handler, + void *data) +{ + struct weston_binding *binding; + + binding = weston_compositor_add_binding(compositor, 0, 0, 0, + modifier, handler, data); + if (binding == NULL) + return NULL; + + wl_list_insert(compositor->touch_binding_list.prev, &binding->link); + + return binding; +} + +WL_EXPORT struct weston_binding * +weston_compositor_add_axis_binding(struct weston_compositor *compositor, + uint32_t axis, uint32_t modifier, + weston_axis_binding_handler_t handler, + void *data) +{ + struct weston_binding *binding; + + binding = weston_compositor_add_binding(compositor, 0, 0, axis, + modifier, handler, data); + if (binding == NULL) + return NULL; + + wl_list_insert(compositor->axis_binding_list.prev, &binding->link); + + return binding; +} + +WL_EXPORT struct weston_binding * +weston_compositor_add_debug_binding(struct weston_compositor *compositor, + uint32_t key, + weston_key_binding_handler_t handler, + void *data) +{ + struct weston_binding *binding; + + binding = weston_compositor_add_binding(compositor, key, 0, 0, 0, + handler, data); + + wl_list_insert(compositor->debug_binding_list.prev, &binding->link); + + return binding; +} + +WL_EXPORT void +weston_binding_destroy(struct weston_binding *binding) +{ + wl_list_remove(&binding->link); + free(binding); +} + +WL_EXPORT void +weston_binding_list_destroy_all(struct wl_list *list) +{ + struct weston_binding *binding, *tmp; + + wl_list_for_each_safe(binding, tmp, list, link) + weston_binding_destroy(binding); +} + +struct binding_keyboard_grab { + uint32_t key; + struct weston_keyboard_grab grab; +}; + +static void +binding_key(struct weston_keyboard_grab *grab, + uint32_t time, uint32_t key, uint32_t state_w) +{ + struct binding_keyboard_grab *b = + container_of(grab, struct binding_keyboard_grab, grab); + struct wl_resource *resource; + enum wl_keyboard_key_state state = state_w; + uint32_t serial; + struct weston_keyboard *keyboard = grab->keyboard; + struct wl_display *display = keyboard->seat->compositor->wl_display; + + if (key == b->key) { + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { + weston_keyboard_end_grab(grab->keyboard); + if (keyboard->input_method_resource) + keyboard->grab = &keyboard->input_method_grab; + free(b); + } + } else if (!wl_list_empty(&keyboard->focus_resource_list)) { + serial = wl_display_next_serial(display); + wl_resource_for_each(resource, &keyboard->focus_resource_list) { + wl_keyboard_send_key(resource, + serial, + time, + key, + state); + } + } +} + +static void +binding_modifiers(struct weston_keyboard_grab *grab, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, &grab->keyboard->focus_resource_list) { + wl_keyboard_send_modifiers(resource, serial, mods_depressed, + mods_latched, mods_locked, group); + } +} + +static void +binding_cancel(struct weston_keyboard_grab *grab) +{ + struct binding_keyboard_grab *binding_grab = + container_of(grab, struct binding_keyboard_grab, grab); + + weston_keyboard_end_grab(grab->keyboard); + free(binding_grab); +} + +static const struct weston_keyboard_grab_interface binding_grab = { + binding_key, + binding_modifiers, + binding_cancel, +}; + +static void +install_binding_grab(struct weston_seat *seat, uint32_t time, uint32_t key) +{ + struct binding_keyboard_grab *grab; + + grab = malloc(sizeof *grab); + grab->key = key; + grab->grab.interface = &binding_grab; + weston_keyboard_start_grab(seat->keyboard, &grab->grab); +} + +WL_EXPORT void +weston_compositor_run_key_binding(struct weston_compositor *compositor, + struct weston_seat *seat, + uint32_t time, uint32_t key, + enum wl_keyboard_key_state state) +{ + struct weston_binding *b; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) + return; + + wl_list_for_each(b, &compositor->key_binding_list, link) { + if (b->key == key && b->modifier == seat->modifier_state) { + weston_key_binding_handler_t handler = b->handler; + handler(seat, time, key, b->data); + + /* If this was a key binding and it didn't + * install a keyboard grab, install one now to + * swallow the key release. */ + if (seat->keyboard->grab == + &seat->keyboard->default_grab) + install_binding_grab(seat, time, key); + } + } +} + +WL_EXPORT void +weston_compositor_run_button_binding(struct weston_compositor *compositor, + struct weston_seat *seat, + uint32_t time, uint32_t button, + enum wl_pointer_button_state state) +{ + struct weston_binding *b; + + if (state == WL_POINTER_BUTTON_STATE_RELEASED) + return; + + wl_list_for_each(b, &compositor->button_binding_list, link) { + if (b->button == button && b->modifier == seat->modifier_state) { + weston_button_binding_handler_t handler = b->handler; + handler(seat, time, button, b->data); + } + } +} + +WL_EXPORT void +weston_compositor_run_touch_binding(struct weston_compositor *compositor, + struct weston_seat *seat, uint32_t time, + int touch_type) +{ + struct weston_binding *b; + + if (seat->num_tp != 1 || touch_type != WL_TOUCH_DOWN) + return; + + wl_list_for_each(b, &compositor->touch_binding_list, link) { + if (b->modifier == seat->modifier_state) { + weston_touch_binding_handler_t handler = b->handler; + handler(seat, time, b->data); + } + } +} + +WL_EXPORT int +weston_compositor_run_axis_binding(struct weston_compositor *compositor, + struct weston_seat *seat, + uint32_t time, uint32_t axis, + wl_fixed_t value) +{ + struct weston_binding *b; + + wl_list_for_each(b, &compositor->axis_binding_list, link) { + if (b->axis == axis && b->modifier == seat->modifier_state) { + weston_axis_binding_handler_t handler = b->handler; + handler(seat, time, axis, value, b->data); + return 1; + } + } + + return 0; +} + +WL_EXPORT int +weston_compositor_run_debug_binding(struct weston_compositor *compositor, + struct weston_seat *seat, + uint32_t time, uint32_t key, + enum wl_keyboard_key_state state) +{ + weston_key_binding_handler_t handler; + struct weston_binding *binding; + int count = 0; + + wl_list_for_each(binding, &compositor->debug_binding_list, link) { + if (key != binding->key) + continue; + + count++; + handler = binding->handler; + handler(seat, time, key, binding->data); + } + + return count; +} diff --git a/src/clipboard.c b/src/clipboard.c new file mode 100644 index 00000000..5a3a02d2 --- /dev/null +++ b/src/clipboard.c @@ -0,0 +1,300 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "compositor.h" + +struct clipboard_source { + struct weston_data_source base; + struct wl_array contents; + struct clipboard *clipboard; + struct wl_event_source *event_source; + uint32_t serial; + int refcount; + int fd; +}; + +struct clipboard { + struct weston_seat *seat; + struct wl_listener selection_listener; + struct wl_listener destroy_listener; + struct clipboard_source *source; +}; + +static void clipboard_client_create(struct clipboard_source *source, int fd); + +static void +clipboard_source_unref(struct clipboard_source *source) +{ + char **s; + + source->refcount--; + if (source->refcount > 0) + return; + + if (source->event_source) { + wl_event_source_remove(source->event_source); + close(source->fd); + } + wl_signal_emit(&source->base.destroy_signal, + &source->base); + s = source->base.mime_types.data; + free(*s); + wl_array_release(&source->base.mime_types); + wl_array_release(&source->contents); + free(source); +} + +static int +clipboard_source_data(int fd, uint32_t mask, void *data) +{ + struct clipboard_source *source = data; + struct clipboard *clipboard = source->clipboard; + char *p; + int len, size; + + if (source->contents.alloc - source->contents.size < 1024) { + wl_array_add(&source->contents, 1024); + source->contents.size -= 1024; + } + + p = source->contents.data + source->contents.size; + size = source->contents.alloc - source->contents.size; + len = read(fd, p, size); + if (len == 0) { + wl_event_source_remove(source->event_source); + close(fd); + source->event_source = NULL; + } else if (len < 0) { + clipboard_source_unref(source); + clipboard->source = NULL; + } else { + source->contents.size += len; + } + + return 1; +} + +static void +clipboard_source_accept(struct weston_data_source *source, + uint32_t time, const char *mime_type) +{ +} + +static void +clipboard_source_send(struct weston_data_source *base, + const char *mime_type, int32_t fd) +{ + struct clipboard_source *source = + container_of(base, struct clipboard_source, base); + char **s; + + s = source->base.mime_types.data; + if (strcmp(mime_type, s[0]) == 0) + clipboard_client_create(source, fd); + else + close(fd); +} + +static void +clipboard_source_cancel(struct weston_data_source *source) +{ +} + +static struct clipboard_source * +clipboard_source_create(struct clipboard *clipboard, + const char *mime_type, uint32_t serial, int fd) +{ + struct wl_display *display = clipboard->seat->compositor->wl_display; + struct wl_event_loop *loop = wl_display_get_event_loop(display); + struct clipboard_source *source; + char **s; + + source = malloc(sizeof *source); + if (source == NULL) + return NULL; + + wl_array_init(&source->contents); + wl_array_init(&source->base.mime_types); + source->base.resource = NULL; + source->base.accept = clipboard_source_accept; + source->base.send = clipboard_source_send; + source->base.cancel = clipboard_source_cancel; + wl_signal_init(&source->base.destroy_signal); + source->refcount = 1; + source->clipboard = clipboard; + source->serial = serial; + + s = wl_array_add(&source->base.mime_types, sizeof *s); + if (s == NULL) + goto err_add; + *s = strdup(mime_type); + if (*s == NULL) + goto err_strdup; + source->event_source = + wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + clipboard_source_data, source); + if (source->event_source == NULL) + goto err_source; + + return source; + + err_source: + free(*s); + err_strdup: + wl_array_release(&source->base.mime_types); + err_add: + free(source); + + return NULL; +} + +struct clipboard_client { + struct wl_event_source *event_source; + size_t offset; + struct clipboard_source *source; +}; + +static int +clipboard_client_data(int fd, uint32_t mask, void *data) +{ + struct clipboard_client *client = data; + char *p; + size_t size; + int len; + + size = client->source->contents.size; + p = client->source->contents.data; + len = write(fd, p + client->offset, size - client->offset); + if (len > 0) + client->offset += len; + + if (client->offset == size || len <= 0) { + close(fd); + wl_event_source_remove(client->event_source); + clipboard_source_unref(client->source); + free(client); + } + + return 1; +} + +static void +clipboard_client_create(struct clipboard_source *source, int fd) +{ + struct weston_seat *seat = source->clipboard->seat; + struct clipboard_client *client; + struct wl_event_loop *loop = + wl_display_get_event_loop(seat->compositor->wl_display); + + client = malloc(sizeof *client); + + client->offset = 0; + client->source = source; + source->refcount++; + client->event_source = + wl_event_loop_add_fd(loop, fd, WL_EVENT_WRITABLE, + clipboard_client_data, client); +} + +static void +clipboard_set_selection(struct wl_listener *listener, void *data) +{ + struct clipboard *clipboard = + container_of(listener, struct clipboard, selection_listener); + struct weston_seat *seat = data; + struct weston_data_source *source = seat->selection_data_source; + const char **mime_types; + int p[2]; + + if (source == NULL) { + if (clipboard->source) + weston_seat_set_selection(seat, + &clipboard->source->base, + clipboard->source->serial); + return; + } else if (source->accept == clipboard_source_accept) { + /* Callback for our data source. */ + return; + } + + if (clipboard->source) + clipboard_source_unref(clipboard->source); + + clipboard->source = NULL; + + mime_types = source->mime_types.data; + + if (pipe2(p, O_CLOEXEC) == -1) + return; + + source->send(source, mime_types[0], p[1]); + + clipboard->source = + clipboard_source_create(clipboard, mime_types[0], + seat->selection_serial, p[0]); + if (clipboard->source == NULL) { + close(p[0]); + return; + } +} + +static void +clipboard_destroy(struct wl_listener *listener, void *data) +{ + struct clipboard *clipboard = + container_of(listener, struct clipboard, destroy_listener); + + wl_list_remove(&clipboard->selection_listener.link); + wl_list_remove(&clipboard->destroy_listener.link); + + free(clipboard); +} + +struct clipboard * +clipboard_create(struct weston_seat *seat) +{ + struct clipboard *clipboard; + + clipboard = zalloc(sizeof *clipboard); + if (clipboard == NULL) + return NULL; + + clipboard->seat = seat; + clipboard->selection_listener.notify = clipboard_set_selection; + clipboard->destroy_listener.notify = clipboard_destroy; + + wl_signal_add(&seat->selection_signal, + &clipboard->selection_listener); + wl_signal_add(&seat->destroy_signal, + &clipboard->destroy_listener); + + return clipboard; +} diff --git a/src/cms-colord.c b/src/cms-colord.c new file mode 100644 index 00000000..4ff3aaca --- /dev/null +++ b/src/cms-colord.c @@ -0,0 +1,554 @@ +/* + * Copyright © 2013 Richard Hughes + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "compositor.h" +#include "cms-helper.h" + +struct cms_colord { + struct weston_compositor *ec; + CdClient *client; + GHashTable *devices; /* key = device-id, value = cms_output */ + GHashTable *pnp_ids; /* key = pnp-id, value = vendor */ + gchar *pnp_ids_data; + GMainLoop *loop; + GThread *thread; + GList *pending; + GMutex pending_mutex; + struct wl_event_source *source; + int readfd; + int writefd; + struct wl_listener destroy_listener; + struct wl_listener output_created_listener; +}; + +struct cms_output { + CdDevice *device; + guint32 backlight_value; + struct cms_colord *cms; + struct weston_color_profile *p; + struct weston_output *o; + struct wl_listener destroy_listener; +}; + +static gint +colord_idle_find_output_cb(gconstpointer a, gconstpointer b) +{ + struct cms_output *ocms = (struct cms_output *) a; + struct weston_output *o = (struct weston_output *) b; + return ocms->o == o ? 0 : -1; +} + +static void +colord_idle_cancel_for_output(struct cms_colord *cms, struct weston_output *o) +{ + GList *l; + + /* cancel and remove any helpers that match the output */ + g_mutex_lock(&cms->pending_mutex); + l = g_list_find_custom (cms->pending, o, colord_idle_find_output_cb); + if (l) { + struct cms_output *ocms = l->data; + cms->pending = g_list_remove (cms->pending, ocms); + } + g_mutex_unlock(&cms->pending_mutex); +} + +static int +edid_value_valid(const char *str) +{ + if (str == NULL) + return 0; + if (str[0] == '\0') + return 0; + if (strcmp(str, "unknown") == 0) + return 0; + return 1; +} + +static gchar * +get_output_id(struct cms_colord *cms, struct weston_output *o) +{ + const gchar *tmp; + GString *device_id; + + /* see https://github.com/hughsie/colord/blob/master/doc/device-and-profile-naming-spec.txt + * for format and allowed values */ + device_id = g_string_new("xrandr"); + if (edid_value_valid(o->make)) { + tmp = g_hash_table_lookup(cms->pnp_ids, o->make); + if (tmp == NULL) + tmp = o->make; + g_string_append_printf(device_id, "-%s", tmp); + } + if (edid_value_valid(o->model)) + g_string_append_printf(device_id, "-%s", o->model); + if (edid_value_valid(o->serial_number)) + g_string_append_printf(device_id, "-%s", o->serial_number); + + /* no EDID data, so use fallback */ + if (strcmp(device_id->str, "xrandr") == 0) + g_string_append_printf(device_id, "-drm-%i", o->id); + + return g_string_free(device_id, FALSE); +} + +static void +update_device_with_profile_in_idle(struct cms_output *ocms) +{ + gboolean signal_write = FALSE; + ssize_t rc; + struct cms_colord *cms = ocms->cms; + + colord_idle_cancel_for_output(cms, ocms->o); + g_mutex_lock(&cms->pending_mutex); + if (cms->pending == NULL) + signal_write = TRUE; + cms->pending = g_list_prepend(cms->pending, ocms); + g_mutex_unlock(&cms->pending_mutex); + + /* signal we've got updates to do */ + if (signal_write) { + gchar tmp = '\0'; + rc = write(cms->writefd, &tmp, 1); + if (rc == 0) + weston_log("colord: failed to write to pending fd"); + } +} + +static void +colord_update_output_from_device (struct cms_output *ocms) +{ + CdProfile *profile; + const gchar *tmp; + gboolean ret; + GError *error = NULL; + gint percentage; + + /* old profile is no longer valid */ + weston_cms_destroy_profile(ocms->p); + ocms->p = NULL; + + ret = cd_device_connect_sync(ocms->device, NULL, &error); + if (!ret) { + weston_log("colord: failed to connect to device %s: %s\n", + cd_device_get_object_path (ocms->device), + error->message); + g_error_free(error); + goto out; + } + profile = cd_device_get_default_profile(ocms->device); + if (!profile) { + weston_log("colord: no assigned color profile for %s\n", + cd_device_get_id (ocms->device)); + goto out; + } + ret = cd_profile_connect_sync(profile, NULL, &error); + if (!ret) { + weston_log("colord: failed to connect to profile %s: %s\n", + cd_profile_get_object_path (profile), + error->message); + g_error_free(error); + goto out; + } + + /* get the calibration brightness level (only set for some profiles) */ + tmp = cd_profile_get_metadata_item(profile, CD_PROFILE_METADATA_SCREEN_BRIGHTNESS); + if (tmp != NULL) { + percentage = atoi(tmp); + if (percentage > 0 && percentage <= 100) + ocms->backlight_value = percentage * 255 / 100; + } + + ocms->p = weston_cms_load_profile(cd_profile_get_filename(profile)); + if (ocms->p == NULL) { + weston_log("colord: warning failed to load profile %s: %s\n", + cd_profile_get_object_path (profile), + error->message); + g_error_free(error); + goto out; + } +out: + update_device_with_profile_in_idle(ocms); +} + +static void +colord_device_changed_cb(CdDevice *device, struct cms_output *ocms) +{ + weston_log("colord: device %s changed, update output\n", + cd_device_get_object_path (ocms->device)); + colord_update_output_from_device(ocms); +} + +static void +colord_notifier_output_destroy(struct wl_listener *listener, void *data) +{ + struct cms_colord *cms = + container_of(listener, struct cms_colord, destroy_listener); + struct weston_output *o = (struct weston_output *) data; + struct cms_output *ocms; + gboolean ret; + gchar *device_id; + GError *error = NULL; + + colord_idle_cancel_for_output(cms, o); + device_id = get_output_id(cms, o); + weston_log("colord: output removed %s\n", device_id); + ocms = g_hash_table_lookup(cms->devices, device_id); + if (!ocms) { + weston_log("colord: failed to delete device\n"); + goto out; + } + g_signal_handlers_disconnect_by_data(ocms->device, ocms); + ret = cd_client_delete_device_sync (cms->client, + ocms->device, + NULL, + &error); + + if (!ret) { + weston_log("colord: failed to delete device: %s\n", error->message); + g_error_free(error); + goto out; + } +out: + g_hash_table_remove (cms->devices, device_id); + g_free (device_id); +} + +static void +colord_output_created(struct cms_colord *cms, struct weston_output *o) +{ + CdDevice *device; + const gchar *tmp; + gchar *device_id; + GError *error = NULL; + GHashTable *device_props; + struct cms_output *ocms; + + /* create device */ + device_id = get_output_id(cms, o); + weston_log("colord: output added %s\n", device_id); + device_props = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + g_hash_table_insert (device_props, + g_strdup(CD_DEVICE_PROPERTY_KIND), + g_strdup(cd_device_kind_to_string (CD_DEVICE_KIND_DISPLAY))); + g_hash_table_insert (device_props, + g_strdup(CD_DEVICE_PROPERTY_FORMAT), + g_strdup("ColorModel.OutputMode.OutputResolution")); + g_hash_table_insert (device_props, + g_strdup(CD_DEVICE_PROPERTY_COLORSPACE), + g_strdup(cd_colorspace_to_string(CD_COLORSPACE_RGB))); + if (edid_value_valid(o->make)) { + tmp = g_hash_table_lookup(cms->pnp_ids, o->make); + if (tmp == NULL) + tmp = o->make; + g_hash_table_insert (device_props, + g_strdup(CD_DEVICE_PROPERTY_VENDOR), + g_strdup(tmp)); + } + if (edid_value_valid(o->model)) { + g_hash_table_insert (device_props, + g_strdup(CD_DEVICE_PROPERTY_MODEL), + g_strdup(o->model)); + } + if (edid_value_valid(o->serial_number)) { + g_hash_table_insert (device_props, + g_strdup(CD_DEVICE_PROPERTY_SERIAL), + g_strdup(o->serial_number)); + } + if (o->connection_internal) { + g_hash_table_insert (device_props, + g_strdup (CD_DEVICE_PROPERTY_EMBEDDED), + NULL); + } + device = cd_client_create_device_sync(cms->client, + device_id, + CD_OBJECT_SCOPE_TEMP, + device_props, + NULL, + &error); + if (g_error_matches (error, + CD_CLIENT_ERROR, + CD_CLIENT_ERROR_ALREADY_EXISTS)) { + g_clear_error(&error); + device = cd_client_find_device_sync (cms->client, + device_id, + NULL, + &error); + } + if (!device) { + weston_log("colord: failed to create new or " + "find existing device: %s\n", + error->message); + g_error_free(error); + goto out; + } + + /* create object and watch for the output to be destroyed */ + ocms = g_slice_new0(struct cms_output); + ocms->cms = cms; + ocms->o = o; + ocms->device = g_object_ref(device); + ocms->destroy_listener.notify = colord_notifier_output_destroy; + wl_signal_add(&o->destroy_signal, &ocms->destroy_listener); + + /* add to local cache */ + g_hash_table_insert (cms->devices, g_strdup(device_id), ocms); + g_signal_connect (ocms->device, "changed", + G_CALLBACK (colord_device_changed_cb), ocms); + + /* get profiles */ + colord_update_output_from_device (ocms); +out: + g_hash_table_unref (device_props); + if (device) + g_object_unref (device); + g_free (device_id); +} + +static void +colord_notifier_output_created(struct wl_listener *listener, void *data) +{ + struct weston_output *o = (struct weston_output *) data; + struct cms_colord *cms = + container_of(listener, struct cms_colord, destroy_listener); + weston_log("colord: output %s created\n", o->name); + colord_output_created(cms, o); +} + +static gpointer +colord_run_loop_thread(gpointer data) +{ + struct cms_colord *cms = (struct cms_colord *) data; + struct weston_output *o; + + /* coldplug outputs */ + wl_list_for_each(o, &cms->ec->output_list, link) { + weston_log("colord: output %s coldplugged\n", o->name); + colord_output_created(cms, o); + } + + g_main_loop_run(cms->loop); + return NULL; +} + +static int +colord_dispatch_all_pending(int fd, uint32_t mask, void *data) +{ + gchar tmp; + GList *l; + ssize_t rc; + struct cms_colord *cms = data; + struct cms_output *ocms; + + weston_log("colord: dispatching events\n"); + g_mutex_lock(&cms->pending_mutex); + for (l = cms->pending; l != NULL; l = l->next) { + ocms = l->data; + + /* optionally set backlight to calibration value */ + if (ocms->o->set_backlight && ocms->backlight_value != 0) { + weston_log("colord: profile calibration backlight to %i/255\n", + ocms->backlight_value); + ocms->o->set_backlight(ocms->o, ocms->backlight_value); + } + + weston_cms_set_color_profile(ocms->o, ocms->p); + } + g_list_free (cms->pending); + cms->pending = NULL; + g_mutex_unlock(&cms->pending_mutex); + + /* done */ + rc = read(cms->readfd, &tmp, 1); + if (rc == 0) + weston_log("colord: failed to read from pending fd"); + return 1; +} + +static void +colord_load_pnp_ids(struct cms_colord *cms) +{ + gboolean ret = FALSE; + gchar *tmp; + GError *error = NULL; + guint i; + const gchar *pnp_ids_fn[] = { "/usr/share/hwdata/pnp.ids", + "/usr/share/misc/pnp.ids", + NULL }; + + /* find and load file */ + for (i = 0; pnp_ids_fn[i] != NULL; i++) { + if (!g_file_test(pnp_ids_fn[i], G_FILE_TEST_EXISTS)) + continue; + ret = g_file_get_contents(pnp_ids_fn[i], + &cms->pnp_ids_data, + NULL, + &error); + if (!ret) { + weston_log("colord: failed to load %s: %s\n", + pnp_ids_fn[i], error->message); + g_error_free(error); + return; + } + break; + } + if (!ret) { + weston_log("colord: no pnp.ids found\n"); + return; + } + + /* parse fixed offsets into lines */ + tmp = cms->pnp_ids_data; + for (i = 0; cms->pnp_ids_data[i] != '\0'; i++) { + if (cms->pnp_ids_data[i] != '\n') + continue; + cms->pnp_ids_data[i] = '\0'; + if (tmp[0] && tmp[1] && tmp[2] && tmp[3] == '\t' && tmp[4]) { + tmp[3] = '\0'; + g_hash_table_insert(cms->pnp_ids, tmp, tmp+4); + tmp = &cms->pnp_ids_data[i+1]; + } + } +} + +static void +colord_module_destroy(struct cms_colord *cms) +{ + g_free(cms->pnp_ids_data); + g_hash_table_unref(cms->pnp_ids); + + if (cms->loop) { + g_main_loop_quit(cms->loop); + g_main_loop_unref(cms->loop); + } + if (cms->thread) + g_thread_join(cms->thread); + if (cms->devices) + g_hash_table_unref(cms->devices); + if (cms->client) + g_object_unref(cms->client); + if (cms->readfd) + close(cms->readfd); + if (cms->writefd) + close(cms->writefd); + free(cms); +} + +static void +colord_notifier_destroy(struct wl_listener *listener, void *data) +{ + struct cms_colord *cms = + container_of(listener, struct cms_colord, destroy_listener); + colord_module_destroy(cms); +} + +static void +colord_cms_output_destroy(gpointer data) +{ + struct cms_output *ocms = (struct cms_output *) data; + g_object_unref(ocms->device); + g_slice_free(struct cms_output, ocms); +} + +WL_EXPORT int +module_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + gboolean ret; + GError *error = NULL; + int fd[2]; + struct cms_colord *cms; + struct wl_event_loop *loop; + + weston_log("colord: initialized\n"); + + /* create local state object */ + cms = zalloc(sizeof *cms); + if (cms == NULL) + return -1; + cms->ec = ec; +#if !GLIB_CHECK_VERSION(2,36,0) + g_type_init(); +#endif + cms->client = cd_client_new(); + ret = cd_client_connect_sync(cms->client, NULL, &error); + if (!ret) { + weston_log("colord: failed to contact daemon: %s\n", error->message); + g_error_free(error); + colord_module_destroy(cms); + return -1; + } + g_mutex_init(&cms->pending_mutex); + cms->devices = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, colord_cms_output_destroy); + + /* destroy */ + cms->destroy_listener.notify = colord_notifier_destroy; + wl_signal_add(&ec->destroy_signal, &cms->destroy_listener); + + /* devices added */ + cms->output_created_listener.notify = colord_notifier_output_created; + wl_signal_add(&ec->output_created_signal, &cms->output_created_listener); + + /* add all the PNP IDs */ + cms->pnp_ids = g_hash_table_new_full(g_str_hash, + g_str_equal, + NULL, + NULL); + colord_load_pnp_ids(cms); + + /* setup a thread for the GLib callbacks */ + cms->loop = g_main_loop_new(NULL, FALSE); + cms->thread = g_thread_new("colord CMS main loop", + colord_run_loop_thread, cms); + + /* batch device<->profile updates */ + if (pipe2(fd, O_CLOEXEC) == -1) { + colord_module_destroy(cms); + return -1; + } + cms->readfd = fd[0]; + cms->writefd = fd[1]; + loop = wl_display_get_event_loop(ec->wl_display); + cms->source = wl_event_loop_add_fd (loop, + cms->readfd, + WL_EVENT_READABLE, + colord_dispatch_all_pending, + cms); + if (!cms->source) { + colord_module_destroy(cms); + return -1; + } + return 0; +} diff --git a/src/cms-helper.c b/src/cms-helper.c new file mode 100644 index 00000000..c063c773 --- /dev/null +++ b/src/cms-helper.c @@ -0,0 +1,132 @@ +/* + * Copyright © 2013 Richard Hughes + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_LCMS +#include +#endif + +#include "compositor.h" +#include "cms-helper.h" + +#ifdef HAVE_LCMS +static void +weston_cms_gamma_clear(struct weston_output *o) +{ + int i; + uint16_t *red; + + if (!o->set_gamma) + return; + + red = calloc(o->gamma_size, sizeof(uint16_t)); + for (i = 0; i < o->gamma_size; i++) + red[i] = (uint32_t) 0xffff * (uint32_t) i / (uint32_t) (o->gamma_size - 1); + o->set_gamma(o, o->gamma_size, red, red, red); + free(red); +} +#endif + +void +weston_cms_set_color_profile(struct weston_output *o, + struct weston_color_profile *p) +{ +#ifdef HAVE_LCMS + cmsFloat32Number in; + const cmsToneCurve **vcgt; + int i; + int size; + uint16_t *red = NULL; + uint16_t *green = NULL; + uint16_t *blue = NULL; + + if (!o->set_gamma) + return; + if (!p) { + weston_cms_gamma_clear(o); + return; + } + + weston_log("Using ICC profile %s\n", p->filename); + vcgt = cmsReadTag (p->lcms_handle, cmsSigVcgtTag); + if (vcgt == NULL || vcgt[0] == NULL) { + weston_cms_gamma_clear(o); + return; + } + + size = o->gamma_size; + red = calloc(size, sizeof(uint16_t)); + green = calloc(size, sizeof(uint16_t)); + blue = calloc(size, sizeof(uint16_t)); + for (i = 0; i < size; i++) { + in = (cmsFloat32Number) i / (cmsFloat32Number) (size - 1); + red[i] = cmsEvalToneCurveFloat(vcgt[0], in) * (double) 0xffff; + green[i] = cmsEvalToneCurveFloat(vcgt[1], in) * (double) 0xffff; + blue[i] = cmsEvalToneCurveFloat(vcgt[2], in) * (double) 0xffff; + } + o->set_gamma(o, size, red, green, blue); + free(red); + free(green); + free(blue); +#endif +} + +void +weston_cms_destroy_profile(struct weston_color_profile *p) +{ + if (!p) + return; +#ifdef HAVE_LCMS + cmsCloseProfile(p->lcms_handle); +#endif + free(p->filename); + free(p); +} + +struct weston_color_profile * +weston_cms_create_profile(const char *filename, + void *lcms_profile) +{ + struct weston_color_profile *p; + p = calloc(1, sizeof(struct weston_color_profile)); + p->filename = strdup(filename); + p->lcms_handle = lcms_profile; + return p; +} + +struct weston_color_profile * +weston_cms_load_profile(const char *filename) +{ + struct weston_color_profile *p = NULL; +#ifdef HAVE_LCMS + cmsHPROFILE lcms_profile; + lcms_profile = cmsOpenProfileFromFile(filename, "r"); + if (lcms_profile) + p = weston_cms_create_profile(filename, lcms_profile); +#endif + return p; +} diff --git a/src/cms-helper.h b/src/cms-helper.h new file mode 100644 index 00000000..6e5594df --- /dev/null +++ b/src/cms-helper.h @@ -0,0 +1,72 @@ +/* + * Copyright © 2013 Richard Hughes + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _WESTON_CMS_H_ +#define _WESTON_CMS_H_ + +#include "config.h" + +#include "compositor.h" + +/* General overview on how to be a CMS plugin: + * + * First, some nomenclature: + * + * CMF: Color management framework, i.e. "Use foo.icc for device $bar" + * CMM: Color management module that converts pixel colors, which is + * usually lcms2 on any modern OS. + * CMS: Color management system that encompasses both a CMF and CMM. + * ICC: International Color Consortium, the people that define the + * binary encoding of a .icc file. + * VCGT: Video Card Gamma Tag. An Apple extension to the ICC specification + * that allows the calibration state to be stored in the ICC profile + * Output: Physical port with a display attached, e.g. LVDS1 + * + * As a CMF is probably something you don't want or need on an embeded install + * these functions will not be called if the icc_profile key is set for a + * specific [output] section in weston.ini + * + * Most desktop environments want the CMF to decide what profile to use in + * different situations, so that displays can be profiled and also so that + * the ICC profiles can be changed at runtime depending on the task or ambient + * environment. + * + * The CMF can be selected using the 'modules' key in the [core] section. + */ + +struct weston_color_profile { + char *filename; + void *lcms_handle; +}; + +void +weston_cms_set_color_profile(struct weston_output *o, + struct weston_color_profile *p); +struct weston_color_profile * +weston_cms_create_profile(const char *filename, + void *lcms_profile); +struct weston_color_profile * +weston_cms_load_profile(const char *filename); +void +weston_cms_destroy_profile(struct weston_color_profile *p); + +#endif diff --git a/src/cms-static.c b/src/cms-static.c new file mode 100644 index 00000000..ad54fd15 --- /dev/null +++ b/src/cms-static.c @@ -0,0 +1,113 @@ +/* + * Copyright © 2013 Richard Hughes + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "compositor.h" +#include "cms-helper.h" + +struct cms_static { + struct weston_compositor *ec; + struct wl_listener destroy_listener; + struct wl_listener output_created_listener; +}; + +static void +cms_output_created(struct cms_static *cms, struct weston_output *o) +{ + struct weston_color_profile *p; + struct weston_config_section *s; + char *profile; + + weston_log("cms-static: output %i [%s] created\n", o->id, o->name); + + if (o->name == NULL) + return; + s = weston_config_get_section(cms->ec->config, + "output", "name", o->name); + if (s == NULL) + return; + if (weston_config_section_get_string(s, "icc_profile", &profile, NULL) < 0) + return; + p = weston_cms_load_profile(profile); + if (p == NULL) { + weston_log("cms-static: failed to load %s\n", profile); + } else { + weston_log("cms-static: loading %s for %s\n", + profile, o->name); + weston_cms_set_color_profile(o, p); + } +} + +static void +cms_notifier_output_created(struct wl_listener *listener, void *data) +{ + struct weston_output *o = (struct weston_output *) data; + struct cms_static *cms = + container_of(listener, struct cms_static, output_created_listener); + cms_output_created(cms, o); +} + +static void +cms_module_destroy(struct cms_static *cms) +{ + free(cms); +} + +static void +cms_notifier_destroy(struct wl_listener *listener, void *data) +{ + struct cms_static *cms = container_of(listener, struct cms_static, destroy_listener); + cms_module_destroy(cms); +} + + +WL_EXPORT int +module_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + struct cms_static *cms; + struct weston_output *output; + + weston_log("cms-static: initialized\n"); + + /* create local state object */ + cms = zalloc(sizeof *cms); + if (cms == NULL) + return -1; + + cms->ec = ec; + cms->destroy_listener.notify = cms_notifier_destroy; + wl_signal_add(&ec->destroy_signal, &cms->destroy_listener); + + cms->output_created_listener.notify = cms_notifier_output_created; + wl_signal_add(&ec->output_created_signal, &cms->output_created_listener); + + /* discover outputs */ + wl_list_for_each(output, &ec->output_list, link) + cms_output_created(cms, output); + + return 0; +} diff --git a/src/compositor-drm.c b/src/compositor-drm.c new file mode 100644 index 00000000..4f015d11 --- /dev/null +++ b/src/compositor-drm.c @@ -0,0 +1,2736 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "compositor.h" +#include "gl-renderer.h" +#include "pixman-renderer.h" +#include "udev-seat.h" +#include "launcher-util.h" +#include "vaapi-recorder.h" + +#ifndef DRM_CAP_TIMESTAMP_MONOTONIC +#define DRM_CAP_TIMESTAMP_MONOTONIC 0x6 +#endif + +static int option_current_mode = 0; + +enum output_config { + OUTPUT_CONFIG_INVALID = 0, + OUTPUT_CONFIG_OFF, + OUTPUT_CONFIG_PREFERRED, + OUTPUT_CONFIG_CURRENT, + OUTPUT_CONFIG_MODE, + OUTPUT_CONFIG_MODELINE +}; + +struct drm_compositor { + struct weston_compositor base; + + struct udev *udev; + struct wl_event_source *drm_source; + + struct udev_monitor *udev_monitor; + struct wl_event_source *udev_drm_source; + + struct { + int id; + int fd; + char *filename; + } drm; + struct gbm_device *gbm; + uint32_t *crtcs; + int num_crtcs; + uint32_t crtc_allocator; + uint32_t connector_allocator; + struct wl_listener session_listener; + uint32_t format; + + /* we need these parameters in order to not fail drmModeAddFB2() + * due to out of bounds dimensions, and then mistakenly set + * sprites_are_broken: + */ + uint32_t min_width, max_width; + uint32_t min_height, max_height; + int no_addfb2; + + struct wl_list sprite_list; + int sprites_are_broken; + int sprites_hidden; + + int cursors_are_broken; + + int use_pixman; + + uint32_t prev_state; + + clockid_t clock; + struct udev_input input; +}; + +struct drm_mode { + struct weston_mode base; + drmModeModeInfo mode_info; +}; + +struct drm_output; + +struct drm_fb { + struct drm_output *output; + uint32_t fb_id, stride, handle, size; + int fd; + int is_client_buffer; + struct weston_buffer_reference buffer_ref; + + /* Used by gbm fbs */ + struct gbm_bo *bo; + + /* Used by dumb fbs */ + void *map; +}; + +struct drm_edid { + char eisa_id[13]; + char monitor_name[13]; + char pnp_id[5]; + char serial_number[13]; +}; + +struct drm_output { + struct weston_output base; + + uint32_t crtc_id; + int pipe; + uint32_t connector_id; + drmModeCrtcPtr original_crtc; + struct drm_edid edid; + drmModePropertyPtr dpms_prop; + + int vblank_pending; + int page_flip_pending; + int destroy_pending; + + struct gbm_surface *surface; + struct gbm_bo *cursor_bo[2]; + struct weston_plane cursor_plane; + struct weston_plane fb_plane; + struct weston_surface *cursor_surface; + int current_cursor; + struct drm_fb *current, *next; + struct backlight *backlight; + + struct drm_fb *dumb[2]; + pixman_image_t *image[2]; + int current_image; + pixman_region32_t previous_damage; + + struct vaapi_recorder *recorder; + struct wl_listener recorder_frame_listener; +}; + +/* + * An output has a primary display plane plus zero or more sprites for + * blending display contents. + */ +struct drm_sprite { + struct wl_list link; + + struct weston_plane plane; + + struct drm_fb *current, *next; + struct drm_output *output; + struct drm_compositor *compositor; + + uint32_t possible_crtcs; + uint32_t plane_id; + uint32_t count_formats; + + int32_t src_x, src_y; + uint32_t src_w, src_h; + uint32_t dest_x, dest_y; + uint32_t dest_w, dest_h; + + uint32_t formats[]; +}; + +static const char default_seat[] = "seat0"; + +static void +drm_output_set_cursor(struct drm_output *output); + +static int +drm_sprite_crtc_supported(struct weston_output *output_base, uint32_t supported) +{ + struct weston_compositor *ec = output_base->compositor; + struct drm_compositor *c =(struct drm_compositor *) ec; + struct drm_output *output = (struct drm_output *) output_base; + int crtc; + + for (crtc = 0; crtc < c->num_crtcs; crtc++) { + if (c->crtcs[crtc] != output->crtc_id) + continue; + + if (supported & (1 << crtc)) + return -1; + } + + return 0; +} + +static void +drm_fb_destroy_callback(struct gbm_bo *bo, void *data) +{ + struct drm_fb *fb = data; + struct gbm_device *gbm = gbm_bo_get_device(bo); + + if (fb->fb_id) + drmModeRmFB(gbm_device_get_fd(gbm), fb->fb_id); + + weston_buffer_reference(&fb->buffer_ref, NULL); + + free(data); +} + +static struct drm_fb * +drm_fb_create_dumb(struct drm_compositor *ec, unsigned width, unsigned height) +{ + struct drm_fb *fb; + int ret; + + struct drm_mode_create_dumb create_arg; + struct drm_mode_destroy_dumb destroy_arg; + struct drm_mode_map_dumb map_arg; + + fb = zalloc(sizeof *fb); + if (!fb) + return NULL; + + memset(&create_arg, 0, sizeof create_arg); + create_arg.bpp = 32; + create_arg.width = width; + create_arg.height = height; + + ret = drmIoctl(ec->drm.fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_arg); + if (ret) + goto err_fb; + + fb->handle = create_arg.handle; + fb->stride = create_arg.pitch; + fb->size = create_arg.size; + fb->fd = ec->drm.fd; + + ret = drmModeAddFB(ec->drm.fd, width, height, 24, 32, + fb->stride, fb->handle, &fb->fb_id); + if (ret) + goto err_bo; + + memset(&map_arg, 0, sizeof map_arg); + map_arg.handle = fb->handle; + ret = drmIoctl(fb->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_arg); + if (ret) + goto err_add_fb; + + fb->map = mmap(0, fb->size, PROT_WRITE, + MAP_SHARED, ec->drm.fd, map_arg.offset); + if (fb->map == MAP_FAILED) + goto err_add_fb; + + return fb; + +err_add_fb: + drmModeRmFB(ec->drm.fd, fb->fb_id); +err_bo: + memset(&destroy_arg, 0, sizeof(destroy_arg)); + destroy_arg.handle = create_arg.handle; + drmIoctl(ec->drm.fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); +err_fb: + free(fb); + return NULL; +} + +static void +drm_fb_destroy_dumb(struct drm_fb *fb) +{ + struct drm_mode_destroy_dumb destroy_arg; + + if (!fb->map) + return; + + if (fb->fb_id) + drmModeRmFB(fb->fd, fb->fb_id); + + weston_buffer_reference(&fb->buffer_ref, NULL); + + munmap(fb->map, fb->size); + + memset(&destroy_arg, 0, sizeof(destroy_arg)); + destroy_arg.handle = fb->handle; + drmIoctl(fb->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_arg); + + free(fb); +} + +static struct drm_fb * +drm_fb_get_from_bo(struct gbm_bo *bo, + struct drm_compositor *compositor, uint32_t format) +{ + struct drm_fb *fb = gbm_bo_get_user_data(bo); + uint32_t width, height; + uint32_t handles[4], pitches[4], offsets[4]; + int ret; + + if (fb) + return fb; + + fb = calloc(1, sizeof *fb); + if (!fb) + return NULL; + + fb->bo = bo; + + width = gbm_bo_get_width(bo); + height = gbm_bo_get_height(bo); + fb->stride = gbm_bo_get_stride(bo); + fb->handle = gbm_bo_get_handle(bo).u32; + fb->size = fb->stride * height; + fb->fd = compositor->drm.fd; + + if (compositor->min_width > width || width > compositor->max_width || + compositor->min_height > height || + height > compositor->max_height) { + weston_log("bo geometry out of bounds\n"); + goto err_free; + } + + ret = -1; + + if (format && !compositor->no_addfb2) { + handles[0] = fb->handle; + pitches[0] = fb->stride; + offsets[0] = 0; + + ret = drmModeAddFB2(compositor->drm.fd, width, height, + format, handles, pitches, offsets, + &fb->fb_id, 0); + if (ret) { + weston_log("addfb2 failed: %m\n"); + compositor->no_addfb2 = 1; + compositor->sprites_are_broken = 1; + } + } + + if (ret) + ret = drmModeAddFB(compositor->drm.fd, width, height, 24, 32, + fb->stride, fb->handle, &fb->fb_id); + + if (ret) { + weston_log("failed to create kms fb: %m\n"); + goto err_free; + } + + gbm_bo_set_user_data(bo, fb, drm_fb_destroy_callback); + + return fb; + +err_free: + free(fb); + return NULL; +} + +static void +drm_fb_set_buffer(struct drm_fb *fb, struct weston_buffer *buffer) +{ + assert(fb->buffer_ref.buffer == NULL); + + fb->is_client_buffer = 1; + + weston_buffer_reference(&fb->buffer_ref, buffer); +} + +static void +drm_output_release_fb(struct drm_output *output, struct drm_fb *fb) +{ + if (!fb) + return; + + if (fb->map && + (fb != output->dumb[0] && fb != output->dumb[1])) { + drm_fb_destroy_dumb(fb); + } else if (fb->bo) { + if (fb->is_client_buffer) + gbm_bo_destroy(fb->bo); + else + gbm_surface_release_buffer(output->surface, + output->current->bo); + } +} + +static uint32_t +drm_output_check_scanout_format(struct drm_output *output, + struct weston_surface *es, struct gbm_bo *bo) +{ + uint32_t format; + pixman_region32_t r; + + format = gbm_bo_get_format(bo); + + switch (format) { + case GBM_FORMAT_XRGB8888: + return format; + case GBM_FORMAT_ARGB8888: + /* We can only scanout an ARGB buffer if the surface's + * opaque region covers the whole output */ + pixman_region32_init(&r); + pixman_region32_subtract(&r, &output->base.region, + &es->opaque); + + if (!pixman_region32_not_empty(&r)) + format = GBM_FORMAT_XRGB8888; + else + format = 0; + + pixman_region32_fini(&r); + + return format; + default: + return 0; + } +} + +static struct weston_plane * +drm_output_prepare_scanout_surface(struct weston_output *_output, + struct weston_surface *es) +{ + struct drm_output *output = (struct drm_output *) _output; + struct drm_compositor *c = + (struct drm_compositor *) output->base.compositor; + struct weston_buffer *buffer = es->buffer_ref.buffer; + struct gbm_bo *bo; + uint32_t format; + + if (es->geometry.x != output->base.x || + es->geometry.y != output->base.y || + buffer == NULL || c->gbm == NULL || + buffer->width != output->base.current_mode->width || + buffer->height != output->base.current_mode->height || + output->base.transform != es->buffer_transform || + es->transform.enabled) + return NULL; + + bo = gbm_bo_import(c->gbm, GBM_BO_IMPORT_WL_BUFFER, + buffer->resource, GBM_BO_USE_SCANOUT); + + /* Unable to use the buffer for scanout */ + if (!bo) + return NULL; + + format = drm_output_check_scanout_format(output, es, bo); + if (format == 0) { + gbm_bo_destroy(bo); + return NULL; + } + + output->next = drm_fb_get_from_bo(bo, c, format); + if (!output->next) { + gbm_bo_destroy(bo); + return NULL; + } + + drm_fb_set_buffer(output->next, buffer); + + return &output->fb_plane; +} + +static void +drm_output_render_gl(struct drm_output *output, pixman_region32_t *damage) +{ + struct drm_compositor *c = + (struct drm_compositor *) output->base.compositor; + struct gbm_bo *bo; + + c->base.renderer->repaint_output(&output->base, damage); + + bo = gbm_surface_lock_front_buffer(output->surface); + if (!bo) { + weston_log("failed to lock front buffer: %m\n"); + return; + } + + output->next = drm_fb_get_from_bo(bo, c, c->format); + if (!output->next) { + weston_log("failed to get drm_fb for bo\n"); + gbm_surface_release_buffer(output->surface, bo); + return; + } +} + +static void +drm_output_render_pixman(struct drm_output *output, pixman_region32_t *damage) +{ + struct weston_compositor *ec = output->base.compositor; + pixman_region32_t total_damage, previous_damage; + + pixman_region32_init(&total_damage); + pixman_region32_init(&previous_damage); + + pixman_region32_copy(&previous_damage, damage); + + pixman_region32_union(&total_damage, damage, &output->previous_damage); + pixman_region32_copy(&output->previous_damage, &previous_damage); + + output->current_image ^= 1; + + output->next = output->dumb[output->current_image]; + pixman_renderer_output_set_buffer(&output->base, + output->image[output->current_image]); + + ec->renderer->repaint_output(&output->base, &total_damage); + + pixman_region32_fini(&total_damage); + pixman_region32_fini(&previous_damage); +} + +static void +drm_output_render(struct drm_output *output, pixman_region32_t *damage) +{ + struct drm_compositor *c = + (struct drm_compositor *) output->base.compositor; + + if (c->use_pixman) + drm_output_render_pixman(output, damage); + else + drm_output_render_gl(output, damage); + + pixman_region32_subtract(&c->base.primary_plane.damage, + &c->base.primary_plane.damage, damage); +} + +static void +drm_output_set_gamma(struct weston_output *output_base, + uint16_t size, uint16_t *r, uint16_t *g, uint16_t *b) +{ + int rc; + struct drm_output *output = (struct drm_output *) output_base; + struct drm_compositor *compositor = (struct drm_compositor *) output->base.compositor; + + /* check */ + if (output_base->gamma_size != size) + return; + if (!output->original_crtc) + return; + + rc = drmModeCrtcSetGamma(compositor->drm.fd, + output->crtc_id, + size, r, g, b); + if (rc) + weston_log("set gamma failed: %m\n"); +} + +static int +drm_output_repaint(struct weston_output *output_base, + pixman_region32_t *damage) +{ + struct drm_output *output = (struct drm_output *) output_base; + struct drm_compositor *compositor = + (struct drm_compositor *) output->base.compositor; + struct drm_sprite *s; + struct drm_mode *mode; + int ret = 0; + + if (output->destroy_pending) + return -1; + + if (!output->next) + drm_output_render(output, damage); + if (!output->next) + return -1; + + mode = container_of(output->base.current_mode, struct drm_mode, base); + if (!output->current) { + ret = drmModeSetCrtc(compositor->drm.fd, output->crtc_id, + output->next->fb_id, 0, 0, + &output->connector_id, 1, + &mode->mode_info); + if (ret) { + weston_log("set mode failed: %m\n"); + goto err_pageflip; + } + output_base->set_dpms(output_base, WESTON_DPMS_ON); + } + + if (drmModePageFlip(compositor->drm.fd, output->crtc_id, + output->next->fb_id, + DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { + weston_log("queueing pageflip failed: %m\n"); + goto err_pageflip; + } + + output->page_flip_pending = 1; + + drm_output_set_cursor(output); + + /* + * Now, update all the sprite surfaces + */ + wl_list_for_each(s, &compositor->sprite_list, link) { + uint32_t flags = 0, fb_id = 0; + drmVBlank vbl = { + .request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT, + .request.sequence = 1, + }; + + if ((!s->current && !s->next) || + !drm_sprite_crtc_supported(output_base, s->possible_crtcs)) + continue; + + if (s->next && !compositor->sprites_hidden) + fb_id = s->next->fb_id; + + ret = drmModeSetPlane(compositor->drm.fd, s->plane_id, + output->crtc_id, fb_id, flags, + s->dest_x, s->dest_y, + s->dest_w, s->dest_h, + s->src_x, s->src_y, + s->src_w, s->src_h); + if (ret) + weston_log("setplane failed: %d: %s\n", + ret, strerror(errno)); + + if (output->pipe > 0) + vbl.request.type |= DRM_VBLANK_SECONDARY; + + /* + * Queue a vblank signal so we know when the surface + * becomes active on the display or has been replaced. + */ + vbl.request.signal = (unsigned long)s; + ret = drmWaitVBlank(compositor->drm.fd, &vbl); + if (ret) { + weston_log("vblank event request failed: %d: %s\n", + ret, strerror(errno)); + } + + s->output = output; + output->vblank_pending = 1; + } + + return 0; + +err_pageflip: + if (output->next) { + drm_output_release_fb(output, output->next); + output->next = NULL; + } + + return -1; +} + +static void +drm_output_start_repaint_loop(struct weston_output *output_base) +{ + struct drm_output *output = (struct drm_output *) output_base; + struct drm_compositor *compositor = (struct drm_compositor *) + output_base->compositor; + uint32_t fb_id; + uint32_t msec; + struct timespec ts; + + if (output->destroy_pending) + return; + + if (!output->current) { + /* We can't page flip if there's no mode set */ + goto finish_frame; + } + + fb_id = output->current->fb_id; + + if (drmModePageFlip(compositor->drm.fd, output->crtc_id, fb_id, + DRM_MODE_PAGE_FLIP_EVENT, output) < 0) { + weston_log("queueing pageflip failed: %m\n"); + goto finish_frame; + } + + return; + +finish_frame: + /* if we cannot page-flip, immediately finish frame */ + clock_gettime(compositor->clock, &ts); + msec = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + weston_output_finish_frame(output_base, msec); +} + +static void +vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, + void *data) +{ + struct drm_sprite *s = (struct drm_sprite *)data; + struct drm_output *output = s->output; + uint32_t msecs; + + output->vblank_pending = 0; + + drm_output_release_fb(output, s->current); + s->current = s->next; + s->next = NULL; + + if (!output->page_flip_pending) { + msecs = sec * 1000 + usec / 1000; + weston_output_finish_frame(&output->base, msecs); + } +} + +static void +drm_output_destroy(struct weston_output *output_base); + +static void +page_flip_handler(int fd, unsigned int frame, + unsigned int sec, unsigned int usec, void *data) +{ + struct drm_output *output = (struct drm_output *) data; + uint32_t msecs; + + /* We don't set page_flip_pending on start_repaint_loop, in that case + * we just want to page flip to the current buffer to get an accurate + * timestamp */ + if (output->page_flip_pending) { + drm_output_release_fb(output, output->current); + output->current = output->next; + output->next = NULL; + } + + output->page_flip_pending = 0; + + if (output->destroy_pending) + drm_output_destroy(&output->base); + else if (!output->vblank_pending) { + msecs = sec * 1000 + usec / 1000; + weston_output_finish_frame(&output->base, msecs); + + /* We can't call this from frame_notify, because the output's + * repaint needed flag is cleared just after that */ + if (output->recorder) + weston_output_schedule_repaint(&output->base); + } +} + +static uint32_t +drm_output_check_sprite_format(struct drm_sprite *s, + struct weston_surface *es, struct gbm_bo *bo) +{ + uint32_t i, format; + + format = gbm_bo_get_format(bo); + + if (format == GBM_FORMAT_ARGB8888) { + pixman_region32_t r; + + pixman_region32_init_rect(&r, 0, 0, + es->geometry.width, + es->geometry.height); + pixman_region32_subtract(&r, &r, &es->opaque); + + if (!pixman_region32_not_empty(&r)) + format = GBM_FORMAT_XRGB8888; + + pixman_region32_fini(&r); + } + + for (i = 0; i < s->count_formats; i++) + if (s->formats[i] == format) + return format; + + return 0; +} + +static int +drm_surface_transform_supported(struct weston_surface *es) +{ + return !es->transform.enabled || + (es->transform.matrix.type < WESTON_MATRIX_TRANSFORM_ROTATE); +} + +static struct weston_plane * +drm_output_prepare_overlay_surface(struct weston_output *output_base, + struct weston_surface *es) +{ + struct weston_compositor *ec = output_base->compositor; + struct drm_compositor *c =(struct drm_compositor *) ec; + struct drm_sprite *s; + int found = 0; + struct gbm_bo *bo; + pixman_region32_t dest_rect, src_rect; + pixman_box32_t *box, tbox; + uint32_t format; + wl_fixed_t sx1, sy1, sx2, sy2; + + if (c->gbm == NULL) + return NULL; + + if (es->buffer_transform != output_base->transform) + return NULL; + + if (es->buffer_scale != output_base->current_scale) + return NULL; + + if (c->sprites_are_broken) + return NULL; + + if (es->output_mask != (1u << output_base->id)) + return NULL; + + if (es->buffer_ref.buffer == NULL) + return NULL; + + if (es->alpha != 1.0f) + return NULL; + + if (wl_shm_buffer_get(es->buffer_ref.buffer->resource)) + return NULL; + + if (!drm_surface_transform_supported(es)) + return NULL; + + wl_list_for_each(s, &c->sprite_list, link) { + if (!drm_sprite_crtc_supported(output_base, s->possible_crtcs)) + continue; + + if (!s->next) { + found = 1; + break; + } + } + + /* No sprites available */ + if (!found) + return NULL; + + bo = gbm_bo_import(c->gbm, GBM_BO_IMPORT_WL_BUFFER, + es->buffer_ref.buffer->resource, + GBM_BO_USE_SCANOUT); + if (!bo) + return NULL; + + format = drm_output_check_sprite_format(s, es, bo); + if (format == 0) { + gbm_bo_destroy(bo); + return NULL; + } + + s->next = drm_fb_get_from_bo(bo, c, format); + if (!s->next) { + gbm_bo_destroy(bo); + return NULL; + } + + drm_fb_set_buffer(s->next, es->buffer_ref.buffer); + + box = pixman_region32_extents(&es->transform.boundingbox); + s->plane.x = box->x1; + s->plane.y = box->y1; + + /* + * Calculate the source & dest rects properly based on actual + * position (note the caller has called weston_surface_update_transform() + * for us already). + */ + pixman_region32_init(&dest_rect); + pixman_region32_intersect(&dest_rect, &es->transform.boundingbox, + &output_base->region); + pixman_region32_translate(&dest_rect, -output_base->x, -output_base->y); + box = pixman_region32_extents(&dest_rect); + tbox = weston_transformed_rect(output_base->width, + output_base->height, + output_base->transform, + output_base->current_scale, + *box); + s->dest_x = tbox.x1; + s->dest_y = tbox.y1; + s->dest_w = tbox.x2 - tbox.x1; + s->dest_h = tbox.y2 - tbox.y1; + pixman_region32_fini(&dest_rect); + + pixman_region32_init(&src_rect); + pixman_region32_intersect(&src_rect, &es->transform.boundingbox, + &output_base->region); + box = pixman_region32_extents(&src_rect); + + weston_surface_from_global_fixed(es, + wl_fixed_from_int(box->x1), + wl_fixed_from_int(box->y1), + &sx1, &sy1); + weston_surface_from_global_fixed(es, + wl_fixed_from_int(box->x2), + wl_fixed_from_int(box->y2), + &sx2, &sy2); + + if (sx1 < 0) + sx1 = 0; + if (sy1 < 0) + sy1 = 0; + if (sx2 > wl_fixed_from_int(es->geometry.width)) + sx2 = wl_fixed_from_int(es->geometry.width); + if (sy2 > wl_fixed_from_int(es->geometry.height)) + sy2 = wl_fixed_from_int(es->geometry.height); + + tbox.x1 = sx1; + tbox.y1 = sy1; + tbox.x2 = sx2; + tbox.y2 = sy2; + + tbox = weston_transformed_rect(wl_fixed_from_int(es->geometry.width), + wl_fixed_from_int(es->geometry.height), + es->buffer_transform, es->buffer_scale, tbox); + + s->src_x = tbox.x1 << 8; + s->src_y = tbox.y1 << 8; + s->src_w = (tbox.x2 - tbox.x1) << 8; + s->src_h = (tbox.y2 - tbox.y1) << 8; + pixman_region32_fini(&src_rect); + + return &s->plane; +} + +static struct weston_plane * +drm_output_prepare_cursor_surface(struct weston_output *output_base, + struct weston_surface *es) +{ + struct drm_compositor *c = + (struct drm_compositor *) output_base->compositor; + struct drm_output *output = (struct drm_output *) output_base; + + if (c->gbm == NULL) + return NULL; + if (output->base.transform != WL_OUTPUT_TRANSFORM_NORMAL) + return NULL; + if (output->cursor_surface) + return NULL; + if (es->output_mask != (1u << output_base->id)) + return NULL; + if (c->cursors_are_broken) + return NULL; + if (es->buffer_ref.buffer == NULL || + !wl_shm_buffer_get(es->buffer_ref.buffer->resource) || + es->geometry.width > 64 || es->geometry.height > 64) + return NULL; + + output->cursor_surface = es; + + return &output->cursor_plane; +} + +static void +drm_output_set_cursor(struct drm_output *output) +{ + struct weston_surface *es = output->cursor_surface; + struct drm_compositor *c = + (struct drm_compositor *) output->base.compositor; + EGLint handle, stride; + struct gbm_bo *bo; + uint32_t buf[64 * 64]; + unsigned char *s; + int i, x, y; + + output->cursor_surface = NULL; + if (es == NULL) { + drmModeSetCursor(c->drm.fd, output->crtc_id, 0, 0, 0); + return; + } + + if (es->buffer_ref.buffer && + pixman_region32_not_empty(&output->cursor_plane.damage)) { + pixman_region32_fini(&output->cursor_plane.damage); + pixman_region32_init(&output->cursor_plane.damage); + output->current_cursor ^= 1; + bo = output->cursor_bo[output->current_cursor]; + memset(buf, 0, sizeof buf); + stride = wl_shm_buffer_get_stride(es->buffer_ref.buffer->shm_buffer); + s = wl_shm_buffer_get_data(es->buffer_ref.buffer->shm_buffer); + for (i = 0; i < es->geometry.height; i++) + memcpy(buf + i * 64, s + i * stride, + es->geometry.width * 4); + + if (gbm_bo_write(bo, buf, sizeof buf) < 0) + weston_log("failed update cursor: %m\n"); + + handle = gbm_bo_get_handle(bo).s32; + if (drmModeSetCursor(c->drm.fd, + output->crtc_id, handle, 64, 64)) { + weston_log("failed to set cursor: %m\n"); + c->cursors_are_broken = 1; + } + } + + x = (es->geometry.x - output->base.x) * output->base.current_scale; + y = (es->geometry.y - output->base.y) * output->base.current_scale; + if (output->cursor_plane.x != x || output->cursor_plane.y != y) { + if (drmModeMoveCursor(c->drm.fd, output->crtc_id, x, y)) { + weston_log("failed to move cursor: %m\n"); + c->cursors_are_broken = 1; + } + + output->cursor_plane.x = x; + output->cursor_plane.y = y; + } +} + +static void +drm_assign_planes(struct weston_output *output) +{ + struct drm_compositor *c = + (struct drm_compositor *) output->compositor; + struct weston_surface *es, *next; + pixman_region32_t overlap, surface_overlap; + struct weston_plane *primary, *next_plane; + + /* + * Find a surface for each sprite in the output using some heuristics: + * 1) size + * 2) frequency of update + * 3) opacity (though some hw might support alpha blending) + * 4) clipping (this can be fixed with color keys) + * + * The idea is to save on blitting since this should save power. + * If we can get a large video surface on the sprite for example, + * the main display surface may not need to update at all, and + * the client buffer can be used directly for the sprite surface + * as we do for flipping full screen surfaces. + */ + pixman_region32_init(&overlap); + primary = &c->base.primary_plane; + wl_list_for_each_safe(es, next, &c->base.surface_list, link) { + /* test whether this buffer can ever go into a plane: + * non-shm, or small enough to be a cursor + */ + if ((es->buffer_ref.buffer && + !wl_shm_buffer_get(es->buffer_ref.buffer->resource)) || + (es->geometry.width <= 64 && es->geometry.height <= 64)) + es->keep_buffer = 1; + else + es->keep_buffer = 0; + + pixman_region32_init(&surface_overlap); + pixman_region32_intersect(&surface_overlap, &overlap, + &es->transform.boundingbox); + + next_plane = NULL; + if (pixman_region32_not_empty(&surface_overlap)) + next_plane = primary; + if (next_plane == NULL) + next_plane = drm_output_prepare_cursor_surface(output, es); + if (next_plane == NULL) + next_plane = drm_output_prepare_scanout_surface(output, es); + if (next_plane == NULL) + next_plane = drm_output_prepare_overlay_surface(output, es); + if (next_plane == NULL) + next_plane = primary; + weston_surface_move_to_plane(es, next_plane); + if (next_plane == primary) + pixman_region32_union(&overlap, &overlap, + &es->transform.boundingbox); + + pixman_region32_fini(&surface_overlap); + } + pixman_region32_fini(&overlap); +} + +static void +drm_output_fini_pixman(struct drm_output *output); + +static void +drm_output_destroy(struct weston_output *output_base) +{ + struct drm_output *output = (struct drm_output *) output_base; + struct drm_compositor *c = + (struct drm_compositor *) output->base.compositor; + drmModeCrtcPtr origcrtc = output->original_crtc; + + if (output->page_flip_pending) { + output->destroy_pending = 1; + weston_log("destroy output while page flip pending\n"); + return; + } + + if (output->backlight) + backlight_destroy(output->backlight); + + drmModeFreeProperty(output->dpms_prop); + + /* Turn off hardware cursor */ + drmModeSetCursor(c->drm.fd, output->crtc_id, 0, 0, 0); + + /* Restore original CRTC state */ + drmModeSetCrtc(c->drm.fd, origcrtc->crtc_id, origcrtc->buffer_id, + origcrtc->x, origcrtc->y, + &output->connector_id, 1, &origcrtc->mode); + drmModeFreeCrtc(origcrtc); + + c->crtc_allocator &= ~(1 << output->crtc_id); + c->connector_allocator &= ~(1 << output->connector_id); + + if (c->use_pixman) { + drm_output_fini_pixman(output); + } else { + gl_renderer_output_destroy(output_base); + gbm_surface_destroy(output->surface); + } + + weston_plane_release(&output->fb_plane); + weston_plane_release(&output->cursor_plane); + + weston_output_destroy(&output->base); + wl_list_remove(&output->base.link); + + free(output); +} + +static struct drm_mode * +choose_mode (struct drm_output *output, struct weston_mode *target_mode) +{ + struct drm_mode *tmp_mode = NULL, *mode; + + if (output->base.current_mode->width == target_mode->width && + output->base.current_mode->height == target_mode->height && + (output->base.current_mode->refresh == target_mode->refresh || + target_mode->refresh == 0)) + return (struct drm_mode *)output->base.current_mode; + + wl_list_for_each(mode, &output->base.mode_list, base.link) { + if (mode->mode_info.hdisplay == target_mode->width && + mode->mode_info.vdisplay == target_mode->height) { + if (mode->mode_info.vrefresh == target_mode->refresh || + target_mode->refresh == 0) { + return mode; + } else if (!tmp_mode) + tmp_mode = mode; + } + } + + return tmp_mode; +} + +static int +drm_output_init_egl(struct drm_output *output, struct drm_compositor *ec); +static int +drm_output_init_pixman(struct drm_output *output, struct drm_compositor *c); + +static int +drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mode) +{ + struct drm_output *output; + struct drm_mode *drm_mode; + struct drm_compositor *ec; + + if (output_base == NULL) { + weston_log("output is NULL.\n"); + return -1; + } + + if (mode == NULL) { + weston_log("mode is NULL.\n"); + return -1; + } + + ec = (struct drm_compositor *)output_base->compositor; + output = (struct drm_output *)output_base; + drm_mode = choose_mode (output, mode); + + if (!drm_mode) { + weston_log("%s, invalid resolution:%dx%d\n", __func__, mode->width, mode->height); + return -1; + } + + if (&drm_mode->base == output->base.current_mode) + return 0; + + output->base.current_mode->flags = 0; + + output->base.current_mode = &drm_mode->base; + output->base.current_mode->flags = + WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + + /* reset rendering stuff. */ + drm_output_release_fb(output, output->current); + drm_output_release_fb(output, output->next); + output->current = output->next = NULL; + + if (ec->use_pixman) { + drm_output_fini_pixman(output); + if (drm_output_init_pixman(output, ec) < 0) { + weston_log("failed to init output pixman state with " + "new mode\n"); + return -1; + } + } else { + gl_renderer_output_destroy(&output->base); + gbm_surface_destroy(output->surface); + + if (drm_output_init_egl(output, ec) < 0) { + weston_log("failed to init output egl state with " + "new mode"); + return -1; + } + } + + return 0; +} + +static int +on_drm_input(int fd, uint32_t mask, void *data) +{ + drmEventContext evctx; + + memset(&evctx, 0, sizeof evctx); + evctx.version = DRM_EVENT_CONTEXT_VERSION; + evctx.page_flip_handler = page_flip_handler; + evctx.vblank_handler = vblank_handler; + drmHandleEvent(fd, &evctx); + + return 1; +} + +static int +init_drm(struct drm_compositor *ec, struct udev_device *device) +{ + const char *filename, *sysnum; + uint64_t cap; + int fd, ret; + + sysnum = udev_device_get_sysnum(device); + if (sysnum) + ec->drm.id = atoi(sysnum); + if (!sysnum || ec->drm.id < 0) { + weston_log("cannot get device sysnum\n"); + return -1; + } + + filename = udev_device_get_devnode(device); + fd = weston_launcher_open(ec->base.launcher, filename, O_RDWR); + if (fd < 0) { + /* Probably permissions error */ + weston_log("couldn't open %s, skipping\n", + udev_device_get_devnode(device)); + return -1; + } + + weston_log("using %s\n", filename); + + ec->drm.fd = fd; + ec->drm.filename = strdup(filename); + + ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); + if (ret == 0 && cap == 1) + ec->clock = CLOCK_MONOTONIC; + else + ec->clock = CLOCK_REALTIME; + + return 0; +} + +static int +init_egl(struct drm_compositor *ec) +{ + EGLint format; + + ec->gbm = gbm_create_device(ec->drm.fd); + + if (!ec->gbm) + return -1; + + format = ec->format; + if (gl_renderer_create(&ec->base, ec->gbm, + gl_renderer_opaque_attribs, &format) < 0) { + gbm_device_destroy(ec->gbm); + return -1; + } + + return 0; +} + +static int +init_pixman(struct drm_compositor *ec) +{ + return pixman_renderer_init(&ec->base); +} + +static struct drm_mode * +drm_output_add_mode(struct drm_output *output, drmModeModeInfo *info) +{ + struct drm_mode *mode; + uint64_t refresh; + + mode = malloc(sizeof *mode); + if (mode == NULL) + return NULL; + + mode->base.flags = 0; + mode->base.width = info->hdisplay; + mode->base.height = info->vdisplay; + + /* Calculate higher precision (mHz) refresh rate */ + refresh = (info->clock * 1000000LL / info->htotal + + info->vtotal / 2) / info->vtotal; + + if (info->flags & DRM_MODE_FLAG_INTERLACE) + refresh *= 2; + if (info->flags & DRM_MODE_FLAG_DBLSCAN) + refresh /= 2; + if (info->vscan > 1) + refresh /= info->vscan; + + mode->base.refresh = refresh; + mode->mode_info = *info; + + if (info->type & DRM_MODE_TYPE_PREFERRED) + mode->base.flags |= WL_OUTPUT_MODE_PREFERRED; + + wl_list_insert(output->base.mode_list.prev, &mode->base.link); + + return mode; +} + +static int +drm_subpixel_to_wayland(int drm_value) +{ + switch (drm_value) { + default: + case DRM_MODE_SUBPIXEL_UNKNOWN: + return WL_OUTPUT_SUBPIXEL_UNKNOWN; + case DRM_MODE_SUBPIXEL_NONE: + return WL_OUTPUT_SUBPIXEL_NONE; + case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: + return WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB; + case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: + return WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR; + case DRM_MODE_SUBPIXEL_VERTICAL_RGB: + return WL_OUTPUT_SUBPIXEL_VERTICAL_RGB; + case DRM_MODE_SUBPIXEL_VERTICAL_BGR: + return WL_OUTPUT_SUBPIXEL_VERTICAL_BGR; + } +} + +/* returns a value between 0-255 range, where higher is brighter */ +static uint32_t +drm_get_backlight(struct drm_output *output) +{ + long brightness, max_brightness, norm; + + brightness = backlight_get_brightness(output->backlight); + max_brightness = backlight_get_max_brightness(output->backlight); + + /* convert it on a scale of 0 to 255 */ + norm = (brightness * 255)/(max_brightness); + + return (uint32_t) norm; +} + +/* values accepted are between 0-255 range */ +static void +drm_set_backlight(struct weston_output *output_base, uint32_t value) +{ + struct drm_output *output = (struct drm_output *) output_base; + long max_brightness, new_brightness; + + if (!output->backlight) + return; + + if (value > 255) + return; + + max_brightness = backlight_get_max_brightness(output->backlight); + + /* get denormalized value */ + new_brightness = (value * max_brightness) / 255; + + backlight_set_brightness(output->backlight, new_brightness); +} + +static drmModePropertyPtr +drm_get_prop(int fd, drmModeConnectorPtr connector, const char *name) +{ + drmModePropertyPtr props; + int i; + + for (i = 0; i < connector->count_props; i++) { + props = drmModeGetProperty(fd, connector->props[i]); + if (!props) + continue; + + if (!strcmp(props->name, name)) + return props; + + drmModeFreeProperty(props); + } + + return NULL; +} + +static void +drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) +{ + struct drm_output *output = (struct drm_output *) output_base; + struct weston_compositor *ec = output_base->compositor; + struct drm_compositor *c = (struct drm_compositor *) ec; + + if (!output->dpms_prop) + return; + + drmModeConnectorSetProperty(c->drm.fd, output->connector_id, + output->dpms_prop->prop_id, level); +} + +static const char *connector_type_names[] = { + "None", + "VGA", + "DVI", + "DVI", + "DVI", + "Composite", + "TV", + "LVDS", + "CTV", + "DIN", + "DP", + "HDMI", + "HDMI", + "TV", + "eDP", +}; + +static int +find_crtc_for_connector(struct drm_compositor *ec, + drmModeRes *resources, drmModeConnector *connector) +{ + drmModeEncoder *encoder; + uint32_t possible_crtcs; + int i, j; + + for (j = 0; j < connector->count_encoders; j++) { + encoder = drmModeGetEncoder(ec->drm.fd, connector->encoders[j]); + if (encoder == NULL) { + weston_log("Failed to get encoder.\n"); + return -1; + } + possible_crtcs = encoder->possible_crtcs; + drmModeFreeEncoder(encoder); + + for (i = 0; i < resources->count_crtcs; i++) { + if (possible_crtcs & (1 << i) && + !(ec->crtc_allocator & (1 << resources->crtcs[i]))) + return i; + } + } + + return -1; +} + +/* Init output state that depends on gl or gbm */ +static int +drm_output_init_egl(struct drm_output *output, struct drm_compositor *ec) +{ + int i, flags; + + output->surface = gbm_surface_create(ec->gbm, + output->base.current_mode->width, + output->base.current_mode->height, + ec->format, + GBM_BO_USE_SCANOUT | + GBM_BO_USE_RENDERING); + if (!output->surface) { + weston_log("failed to create gbm surface\n"); + return -1; + } + + if (gl_renderer_output_create(&output->base, output->surface) < 0) { + weston_log("failed to create gl renderer output state\n"); + gbm_surface_destroy(output->surface); + return -1; + } + + flags = GBM_BO_USE_CURSOR_64X64 | GBM_BO_USE_WRITE; + + for (i = 0; i < 2; i++) { + if (output->cursor_bo[i]) + continue; + + output->cursor_bo[i] = + gbm_bo_create(ec->gbm, 64, 64, GBM_FORMAT_ARGB8888, + flags); + } + + if (output->cursor_bo[0] == NULL || output->cursor_bo[1] == NULL) { + weston_log("cursor buffers unavailable, using gl cursors\n"); + ec->cursors_are_broken = 1; + } + + return 0; +} + +static int +drm_output_init_pixman(struct drm_output *output, struct drm_compositor *c) +{ + int w = output->base.current_mode->width; + int h = output->base.current_mode->height; + unsigned int i; + + /* FIXME error checking */ + + for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { + output->dumb[i] = drm_fb_create_dumb(c, w, h); + if (!output->dumb[i]) + goto err; + + output->image[i] = + pixman_image_create_bits(PIXMAN_x8r8g8b8, w, h, + output->dumb[i]->map, + output->dumb[i]->stride); + if (!output->image[i]) + goto err; + } + + if (pixman_renderer_output_create(&output->base) < 0) + goto err; + + pixman_region32_init_rect(&output->previous_damage, + output->base.x, output->base.y, output->base.width, output->base.height); + + return 0; + +err: + for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { + if (output->dumb[i]) + drm_fb_destroy_dumb(output->dumb[i]); + if (output->image[i]) + pixman_image_unref(output->image[i]); + + output->dumb[i] = NULL; + output->image[i] = NULL; + } + + return -1; +} + +static void +drm_output_fini_pixman(struct drm_output *output) +{ + unsigned int i; + + pixman_renderer_output_destroy(&output->base); + pixman_region32_fini(&output->previous_damage); + + for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { + drm_fb_destroy_dumb(output->dumb[i]); + pixman_image_unref(output->image[i]); + output->dumb[i] = NULL; + output->image[i] = NULL; + } +} + +static void +edid_parse_string(const uint8_t *data, char text[]) +{ + int i; + int replaced = 0; + + /* this is always 12 bytes, but we can't guarantee it's null + * terminated or not junk. */ + strncpy(text, (const char *) data, 12); + + /* remove insane chars */ + for (i = 0; text[i] != '\0'; i++) { + if (text[i] == '\n' || + text[i] == '\r') { + text[i] = '\0'; + break; + } + } + + /* ensure string is printable */ + for (i = 0; text[i] != '\0'; i++) { + if (!isprint(text[i])) { + text[i] = '-'; + replaced++; + } + } + + /* if the string is random junk, ignore the string */ + if (replaced > 4) + text[0] = '\0'; +} + +#define EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe +#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc +#define EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff +#define EDID_OFFSET_DATA_BLOCKS 0x36 +#define EDID_OFFSET_LAST_BLOCK 0x6c +#define EDID_OFFSET_PNPID 0x08 +#define EDID_OFFSET_SERIAL 0x0c + +static int +edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length) +{ + int i; + uint32_t serial_number; + + /* check header */ + if (length < 128) + return -1; + if (data[0] != 0x00 || data[1] != 0xff) + return -1; + + /* decode the PNP ID from three 5 bit words packed into 2 bytes + * /--08--\/--09--\ + * 7654321076543210 + * |\---/\---/\---/ + * R C1 C2 C3 */ + edid->pnp_id[0] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1; + edid->pnp_id[1] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x3) * 8) + ((data[EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1; + edid->pnp_id[2] = 'A' + (data[EDID_OFFSET_PNPID + 1] & 0x1f) - 1; + edid->pnp_id[3] = '\0'; + + /* maybe there isn't a ASCII serial number descriptor, so use this instead */ + serial_number = (uint32_t) data[EDID_OFFSET_SERIAL + 0]; + serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 1] * 0x100; + serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 2] * 0x10000; + serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 3] * 0x1000000; + if (serial_number > 0) + sprintf(edid->serial_number, "%lu", (unsigned long) serial_number); + + /* parse EDID data */ + for (i = EDID_OFFSET_DATA_BLOCKS; + i <= EDID_OFFSET_LAST_BLOCK; + i += 18) { + /* ignore pixel clock data */ + if (data[i] != 0) + continue; + if (data[i+2] != 0) + continue; + + /* any useful blocks? */ + if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME) { + edid_parse_string(&data[i+5], + edid->monitor_name); + } else if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) { + edid_parse_string(&data[i+5], + edid->serial_number); + } else if (data[i+3] == EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) { + edid_parse_string(&data[i+5], + edid->eisa_id); + } + } + return 0; +} + +static void +find_and_parse_output_edid(struct drm_compositor *ec, + struct drm_output *output, + drmModeConnector *connector) +{ + drmModePropertyBlobPtr edid_blob = NULL; + drmModePropertyPtr property; + int i; + int rc; + + for (i = 0; i < connector->count_props && !edid_blob; i++) { + property = drmModeGetProperty(ec->drm.fd, connector->props[i]); + if (!property) + continue; + if ((property->flags & DRM_MODE_PROP_BLOB) && + !strcmp(property->name, "EDID")) { + edid_blob = drmModeGetPropertyBlob(ec->drm.fd, + connector->prop_values[i]); + } + drmModeFreeProperty(property); + } + if (!edid_blob) + return; + + rc = edid_parse(&output->edid, + edid_blob->data, + edid_blob->length); + if (!rc) { + weston_log("EDID data '%s', '%s', '%s'\n", + output->edid.pnp_id, + output->edid.monitor_name, + output->edid.serial_number); + if (output->edid.pnp_id[0] != '\0') + output->base.make = output->edid.pnp_id; + if (output->edid.monitor_name[0] != '\0') + output->base.model = output->edid.monitor_name; + if (output->edid.serial_number[0] != '\0') + output->base.serial_number = output->edid.serial_number; + } + drmModeFreePropertyBlob(edid_blob); +} + + + +static int +parse_modeline(const char *s, drmModeModeInfo *mode) +{ + char hsync[16]; + char vsync[16]; + float fclock; + + mode->type = DRM_MODE_TYPE_USERDEF; + mode->hskew = 0; + mode->vscan = 0; + mode->vrefresh = 0; + mode->flags = 0; + + if (sscanf(s, "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s", + &fclock, + &mode->hdisplay, + &mode->hsync_start, + &mode->hsync_end, + &mode->htotal, + &mode->vdisplay, + &mode->vsync_start, + &mode->vsync_end, + &mode->vtotal, hsync, vsync) != 11) + return -1; + + mode->clock = fclock * 1000; + if (strcmp(hsync, "+hsync") == 0) + mode->flags |= DRM_MODE_FLAG_PHSYNC; + else if (strcmp(hsync, "-hsync") == 0) + mode->flags |= DRM_MODE_FLAG_NHSYNC; + else + return -1; + + if (strcmp(vsync, "+vsync") == 0) + mode->flags |= DRM_MODE_FLAG_PVSYNC; + else if (strcmp(vsync, "-vsync") == 0) + mode->flags |= DRM_MODE_FLAG_NVSYNC; + else + return -1; + + return 0; +} + +static uint32_t +parse_transform(const char *transform, const char *output_name) +{ + static const struct { const char *name; uint32_t token; } names[] = { + { "normal", WL_OUTPUT_TRANSFORM_NORMAL }, + { "90", WL_OUTPUT_TRANSFORM_90 }, + { "180", WL_OUTPUT_TRANSFORM_180 }, + { "270", WL_OUTPUT_TRANSFORM_270 }, + { "flipped", WL_OUTPUT_TRANSFORM_FLIPPED }, + { "flipped-90", WL_OUTPUT_TRANSFORM_FLIPPED_90 }, + { "flipped-180", WL_OUTPUT_TRANSFORM_FLIPPED_180 }, + { "flipped-270", WL_OUTPUT_TRANSFORM_FLIPPED_270 }, + }; + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(names); i++) + if (strcmp(names[i].name, transform) == 0) + return names[i].token; + + weston_log("Invalid transform \"%s\" for output %s\n", + transform, output_name); + + return WL_OUTPUT_TRANSFORM_NORMAL; +} + +static void +setup_output_seat_constraint(struct drm_compositor *ec, + struct weston_output *output, + const char *s) +{ + if (strcmp(s, "") != 0) { + struct udev_seat *seat; + + seat = udev_seat_get_named(&ec->base, s); + if (seat) + seat->base.output = output; + + if (seat && seat->base.pointer) + weston_pointer_clamp(seat->base.pointer, + &seat->base.pointer->x, + &seat->base.pointer->y); + } +} + +static int +create_output_for_connector(struct drm_compositor *ec, + drmModeRes *resources, + drmModeConnector *connector, + int x, int y, struct udev_device *drm_device) +{ + struct drm_output *output; + struct drm_mode *drm_mode, *next, *preferred, *current, *configured; + struct weston_mode *m; + struct weston_config_section *section; + drmModeEncoder *encoder; + drmModeModeInfo crtc_mode, modeline; + drmModeCrtc *crtc; + int i, width, height, scale; + char name[32], *s; + const char *type_name; + enum output_config config; + uint32_t transform; + + i = find_crtc_for_connector(ec, resources, connector); + if (i < 0) { + weston_log("No usable crtc/encoder pair for connector.\n"); + return -1; + } + + output = zalloc(sizeof *output); + if (output == NULL) + return -1; + + output->base.subpixel = drm_subpixel_to_wayland(connector->subpixel); + output->base.make = "unknown"; + output->base.model = "unknown"; + output->base.serial_number = "unknown"; + wl_list_init(&output->base.mode_list); + + if (connector->connector_type < ARRAY_LENGTH(connector_type_names)) + type_name = connector_type_names[connector->connector_type]; + else + type_name = "UNKNOWN"; + snprintf(name, 32, "%s%d", type_name, connector->connector_type_id); + output->base.name = strdup(name); + + section = weston_config_get_section(ec->base.config, "output", "name", + output->base.name); + weston_config_section_get_string(section, "mode", &s, "preferred"); + if (strcmp(s, "off") == 0) + config = OUTPUT_CONFIG_OFF; + else if (strcmp(s, "preferred") == 0) + config = OUTPUT_CONFIG_PREFERRED; + else if (strcmp(s, "current") == 0) + config = OUTPUT_CONFIG_CURRENT; + else if (sscanf(s, "%dx%d", &width, &height) == 2) + config = OUTPUT_CONFIG_MODE; + else if (parse_modeline(s, &modeline) == 0) + config = OUTPUT_CONFIG_MODELINE; + else { + weston_log("Invalid mode \"%s\" for output %s\n", + s, output->base.name); + config = OUTPUT_CONFIG_PREFERRED; + } + free(s); + + weston_config_section_get_int(section, "scale", &scale, 1); + weston_config_section_get_string(section, "transform", &s, "normal"); + transform = parse_transform(s, output->base.name); + free(s); + + weston_config_section_get_string(section, "seat", &s, ""); + setup_output_seat_constraint(ec, &output->base, s); + free(s); + + output->crtc_id = resources->crtcs[i]; + output->pipe = i; + ec->crtc_allocator |= (1 << output->crtc_id); + output->connector_id = connector->connector_id; + ec->connector_allocator |= (1 << output->connector_id); + + output->original_crtc = drmModeGetCrtc(ec->drm.fd, output->crtc_id); + output->dpms_prop = drm_get_prop(ec->drm.fd, connector, "DPMS"); + + /* Get the current mode on the crtc that's currently driving + * this connector. */ + encoder = drmModeGetEncoder(ec->drm.fd, connector->encoder_id); + memset(&crtc_mode, 0, sizeof crtc_mode); + if (encoder != NULL) { + crtc = drmModeGetCrtc(ec->drm.fd, encoder->crtc_id); + drmModeFreeEncoder(encoder); + if (crtc == NULL) + goto err_free; + if (crtc->mode_valid) + crtc_mode = crtc->mode; + drmModeFreeCrtc(crtc); + } + + for (i = 0; i < connector->count_modes; i++) { + drm_mode = drm_output_add_mode(output, &connector->modes[i]); + if (!drm_mode) + goto err_free; + } + + if (config == OUTPUT_CONFIG_OFF) { + weston_log("Disabling output %s\n", output->base.name); + drmModeSetCrtc(ec->drm.fd, output->crtc_id, + 0, 0, 0, 0, 0, NULL); + goto err_free; + } + + preferred = NULL; + current = NULL; + configured = NULL; + + wl_list_for_each_reverse(drm_mode, &output->base.mode_list, base.link) { + if (config == OUTPUT_CONFIG_MODE && + width == drm_mode->base.width && + height == drm_mode->base.height) + configured = drm_mode; + if (!memcmp(&crtc_mode, &drm_mode->mode_info, sizeof crtc_mode)) + current = drm_mode; + if (drm_mode->base.flags & WL_OUTPUT_MODE_PREFERRED) + preferred = drm_mode; + } + + if (config == OUTPUT_CONFIG_MODELINE) { + configured = drm_output_add_mode(output, &modeline); + if (!configured) + goto err_free; + } + + if (current == NULL && crtc_mode.clock != 0) { + current = drm_output_add_mode(output, &crtc_mode); + if (!current) + goto err_free; + } + + if (config == OUTPUT_CONFIG_CURRENT) + configured = current; + + if (option_current_mode && current) + output->base.current_mode = ¤t->base; + else if (configured) + output->base.current_mode = &configured->base; + else if (preferred) + output->base.current_mode = &preferred->base; + else if (current) + output->base.current_mode = ¤t->base; + + if (output->base.current_mode == NULL) { + weston_log("no available modes for %s\n", output->base.name); + goto err_free; + } + + output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT; + + weston_output_init(&output->base, &ec->base, x, y, + connector->mmWidth, connector->mmHeight, + transform, scale); + + if (ec->use_pixman) { + if (drm_output_init_pixman(output, ec) < 0) { + weston_log("Failed to init output pixman state\n"); + goto err_output; + } + } else if (drm_output_init_egl(output, ec) < 0) { + weston_log("Failed to init output gl state\n"); + goto err_output; + } + + output->backlight = backlight_init(drm_device, + connector->connector_type); + if (output->backlight) { + weston_log("Initialized backlight, device %s\n", + output->backlight->path); + output->base.set_backlight = drm_set_backlight; + output->base.backlight_current = drm_get_backlight(output); + } else { + weston_log("Failed to initialize backlight\n"); + } + + wl_list_insert(ec->base.output_list.prev, &output->base.link); + + find_and_parse_output_edid(ec, output, connector); + if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) + output->base.connection_internal = 1; + + output->base.start_repaint_loop = drm_output_start_repaint_loop; + output->base.repaint = drm_output_repaint; + output->base.destroy = drm_output_destroy; + output->base.assign_planes = drm_assign_planes; + output->base.set_dpms = drm_set_dpms; + output->base.switch_mode = drm_output_switch_mode; + + output->base.gamma_size = output->original_crtc->gamma_size; + output->base.set_gamma = drm_output_set_gamma; + + weston_plane_init(&output->cursor_plane, &ec->base, 0, 0); + weston_plane_init(&output->fb_plane, &ec->base, 0, 0); + + weston_compositor_stack_plane(&ec->base, &output->cursor_plane, NULL); + weston_compositor_stack_plane(&ec->base, &output->fb_plane, + &ec->base.primary_plane); + + weston_log("Output %s, (connector %d, crtc %d)\n", + output->base.name, output->connector_id, output->crtc_id); + wl_list_for_each(m, &output->base.mode_list, link) + weston_log_continue(" mode %dx%d@%.1f%s%s%s\n", + m->width, m->height, m->refresh / 1000.0, + m->flags & WL_OUTPUT_MODE_PREFERRED ? + ", preferred" : "", + m->flags & WL_OUTPUT_MODE_CURRENT ? + ", current" : "", + connector->count_modes == 0 ? + ", built-in" : ""); + + return 0; + +err_output: + weston_output_destroy(&output->base); +err_free: + wl_list_for_each_safe(drm_mode, next, &output->base.mode_list, + base.link) { + wl_list_remove(&drm_mode->base.link); + free(drm_mode); + } + + drmModeFreeCrtc(output->original_crtc); + ec->crtc_allocator &= ~(1 << output->crtc_id); + ec->connector_allocator &= ~(1 << output->connector_id); + free(output); + + return -1; +} + +static void +create_sprites(struct drm_compositor *ec) +{ + struct drm_sprite *sprite; + drmModePlaneRes *plane_res; + drmModePlane *plane; + uint32_t i; + + plane_res = drmModeGetPlaneResources(ec->drm.fd); + if (!plane_res) { + weston_log("failed to get plane resources: %s\n", + strerror(errno)); + return; + } + + for (i = 0; i < plane_res->count_planes; i++) { + plane = drmModeGetPlane(ec->drm.fd, plane_res->planes[i]); + if (!plane) + continue; + + sprite = zalloc(sizeof(*sprite) + ((sizeof(uint32_t)) * + plane->count_formats)); + if (!sprite) { + weston_log("%s: out of memory\n", + __func__); + free(plane); + continue; + } + + sprite->possible_crtcs = plane->possible_crtcs; + sprite->plane_id = plane->plane_id; + sprite->current = NULL; + sprite->next = NULL; + sprite->compositor = ec; + sprite->count_formats = plane->count_formats; + memcpy(sprite->formats, plane->formats, + plane->count_formats * sizeof(plane->formats[0])); + drmModeFreePlane(plane); + weston_plane_init(&sprite->plane, &ec->base, 0, 0); + weston_compositor_stack_plane(&ec->base, &sprite->plane, + &ec->base.primary_plane); + + wl_list_insert(&ec->sprite_list, &sprite->link); + } + + drmModeFreePlaneResources(plane_res); +} + +static void +destroy_sprites(struct drm_compositor *compositor) +{ + struct drm_sprite *sprite, *next; + struct drm_output *output; + + output = container_of(compositor->base.output_list.next, + struct drm_output, base.link); + + wl_list_for_each_safe(sprite, next, &compositor->sprite_list, link) { + drmModeSetPlane(compositor->drm.fd, + sprite->plane_id, + output->crtc_id, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0); + drm_output_release_fb(output, sprite->current); + drm_output_release_fb(output, sprite->next); + weston_plane_release(&sprite->plane); + free(sprite); + } +} + +static int +create_outputs(struct drm_compositor *ec, uint32_t option_connector, + struct udev_device *drm_device) +{ + drmModeConnector *connector; + drmModeRes *resources; + int i; + int x = 0, y = 0; + + resources = drmModeGetResources(ec->drm.fd); + if (!resources) { + weston_log("drmModeGetResources failed\n"); + return -1; + } + + ec->crtcs = calloc(resources->count_crtcs, sizeof(uint32_t)); + if (!ec->crtcs) { + drmModeFreeResources(resources); + return -1; + } + + ec->min_width = resources->min_width; + ec->max_width = resources->max_width; + ec->min_height = resources->min_height; + ec->max_height = resources->max_height; + + ec->num_crtcs = resources->count_crtcs; + memcpy(ec->crtcs, resources->crtcs, sizeof(uint32_t) * ec->num_crtcs); + + for (i = 0; i < resources->count_connectors; i++) { + connector = drmModeGetConnector(ec->drm.fd, + resources->connectors[i]); + if (connector == NULL) + continue; + + if (connector->connection == DRM_MODE_CONNECTED && + (option_connector == 0 || + connector->connector_id == option_connector)) { + if (create_output_for_connector(ec, resources, + connector, x, y, + drm_device) < 0) { + drmModeFreeConnector(connector); + continue; + } + + x += container_of(ec->base.output_list.prev, + struct weston_output, + link)->width; + } + + drmModeFreeConnector(connector); + } + + if (wl_list_empty(&ec->base.output_list)) { + weston_log("No currently active connector found.\n"); + drmModeFreeResources(resources); + return -1; + } + + drmModeFreeResources(resources); + + return 0; +} + +static void +update_outputs(struct drm_compositor *ec, struct udev_device *drm_device) +{ + drmModeConnector *connector; + drmModeRes *resources; + struct drm_output *output, *next; + int x = 0, y = 0; + int x_offset = 0, y_offset = 0; + uint32_t connected = 0, disconnects = 0; + int i; + + resources = drmModeGetResources(ec->drm.fd); + if (!resources) { + weston_log("drmModeGetResources failed\n"); + return; + } + + /* collect new connects */ + for (i = 0; i < resources->count_connectors; i++) { + int connector_id = resources->connectors[i]; + + connector = drmModeGetConnector(ec->drm.fd, connector_id); + if (connector == NULL) + continue; + + if (connector->connection != DRM_MODE_CONNECTED) { + drmModeFreeConnector(connector); + continue; + } + + connected |= (1 << connector_id); + + if (!(ec->connector_allocator & (1 << connector_id))) { + struct weston_output *last = + container_of(ec->base.output_list.prev, + struct weston_output, link); + + /* XXX: not yet needed, we die with 0 outputs */ + if (!wl_list_empty(&ec->base.output_list)) + x = last->x + last->width; + else + x = 0; + y = 0; + create_output_for_connector(ec, resources, + connector, x, y, + drm_device); + weston_log("connector %d connected\n", connector_id); + + } + drmModeFreeConnector(connector); + } + drmModeFreeResources(resources); + + disconnects = ec->connector_allocator & ~connected; + if (disconnects) { + wl_list_for_each_safe(output, next, &ec->base.output_list, + base.link) { + if (x_offset != 0 || y_offset != 0) { + weston_output_move(&output->base, + output->base.x - x_offset, + output->base.y - y_offset); + } + + if (disconnects & (1 << output->connector_id)) { + disconnects &= ~(1 << output->connector_id); + weston_log("connector %d disconnected\n", + output->connector_id); + x_offset += output->base.width; + drm_output_destroy(&output->base); + } + } + } + + /* FIXME: handle zero outputs, without terminating */ + if (ec->connector_allocator == 0) + wl_display_terminate(ec->base.wl_display); +} + +static int +udev_event_is_hotplug(struct drm_compositor *ec, struct udev_device *device) +{ + const char *sysnum; + const char *val; + + sysnum = udev_device_get_sysnum(device); + if (!sysnum || atoi(sysnum) != ec->drm.id) + return 0; + + val = udev_device_get_property_value(device, "HOTPLUG"); + if (!val) + return 0; + + return strcmp(val, "1") == 0; +} + +static int +udev_drm_event(int fd, uint32_t mask, void *data) +{ + struct drm_compositor *ec = data; + struct udev_device *event; + + event = udev_monitor_receive_device(ec->udev_monitor); + + if (udev_event_is_hotplug(ec, event)) + update_outputs(ec, event); + + udev_device_unref(event); + + return 1; +} + +static void +drm_restore(struct weston_compositor *ec) +{ + weston_launcher_restore(ec->launcher); +} + +static void +drm_destroy(struct weston_compositor *ec) +{ + struct drm_compositor *d = (struct drm_compositor *) ec; + + udev_input_destroy(&d->input); + + wl_event_source_remove(d->udev_drm_source); + wl_event_source_remove(d->drm_source); + + destroy_sprites(d); + + weston_compositor_shutdown(ec); + + ec->renderer->destroy(ec); + + if (d->gbm) + gbm_device_destroy(d->gbm); + + weston_launcher_destroy(d->base.launcher); + + close(d->drm.fd); + + free(d); +} + +static void +drm_compositor_set_modes(struct drm_compositor *compositor) +{ + struct drm_output *output; + struct drm_mode *drm_mode; + int ret; + + wl_list_for_each(output, &compositor->base.output_list, base.link) { + if (!output->current) { + /* If something that would cause the output to + * switch mode happened while in another vt, we + * might not have a current drm_fb. In that case, + * schedule a repaint and let drm_output_repaint + * handle setting the mode. */ + weston_output_schedule_repaint(&output->base); + continue; + } + + drm_mode = (struct drm_mode *) output->base.current_mode; + ret = drmModeSetCrtc(compositor->drm.fd, output->crtc_id, + output->current->fb_id, 0, 0, + &output->connector_id, 1, + &drm_mode->mode_info); + if (ret < 0) { + weston_log( + "failed to set mode %dx%d for output at %d,%d: %m\n", + drm_mode->base.width, drm_mode->base.height, + output->base.x, output->base.y); + } + } +} + +static void +session_notify(struct wl_listener *listener, void *data) +{ + struct weston_compositor *compositor = data; + struct drm_compositor *ec = data; + struct drm_sprite *sprite; + struct drm_output *output; + + if (ec->base.session_active) { + weston_log("activating session\n"); + compositor->focus = 1; + compositor->state = ec->prev_state; + drm_compositor_set_modes(ec); + weston_compositor_damage_all(compositor); + udev_input_enable(&ec->input, ec->udev); + } else { + weston_log("deactivating session\n"); + udev_input_disable(&ec->input); + + compositor->focus = 0; + ec->prev_state = compositor->state; + weston_compositor_offscreen(compositor); + + /* If we have a repaint scheduled (either from a + * pending pageflip or the idle handler), make sure we + * cancel that so we don't try to pageflip when we're + * vt switched away. The OFFSCREEN state will prevent + * further attemps at repainting. When we switch + * back, we schedule a repaint, which will process + * pending frame callbacks. */ + + wl_list_for_each(output, &ec->base.output_list, base.link) { + output->base.repaint_needed = 0; + drmModeSetCursor(ec->drm.fd, output->crtc_id, 0, 0, 0); + } + + output = container_of(ec->base.output_list.next, + struct drm_output, base.link); + + wl_list_for_each(sprite, &ec->sprite_list, link) + drmModeSetPlane(ec->drm.fd, + sprite->plane_id, + output->crtc_id, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0); + }; +} + +static void +switch_vt_binding(struct weston_seat *seat, uint32_t time, uint32_t key, void *data) +{ + struct weston_compositor *compositor = data; + + weston_launcher_activate_vt(compositor->launcher, key - KEY_F1 + 1); +} + +/* + * Find primary GPU + * Some systems may have multiple DRM devices attached to a single seat. This + * function loops over all devices and tries to find a PCI device with the + * boot_vga sysfs attribute set to 1. + * If no such device is found, the first DRM device reported by udev is used. + */ +static struct udev_device* +find_primary_gpu(struct drm_compositor *ec, const char *seat) +{ + struct udev_enumerate *e; + struct udev_list_entry *entry; + const char *path, *device_seat, *id; + struct udev_device *device, *drm_device, *pci; + + e = udev_enumerate_new(ec->udev); + udev_enumerate_add_match_subsystem(e, "drm"); + udev_enumerate_add_match_sysname(e, "card[0-9]*"); + + udev_enumerate_scan_devices(e); + drm_device = NULL; + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { + path = udev_list_entry_get_name(entry); + device = udev_device_new_from_syspath(ec->udev, path); + if (!device) + continue; + device_seat = udev_device_get_property_value(device, "ID_SEAT"); + if (!device_seat) + device_seat = default_seat; + if (strcmp(device_seat, seat)) { + udev_device_unref(device); + continue; + } + + pci = udev_device_get_parent_with_subsystem_devtype(device, + "pci", NULL); + if (pci) { + id = udev_device_get_sysattr_value(pci, "boot_vga"); + if (id && !strcmp(id, "1")) { + if (drm_device) + udev_device_unref(drm_device); + drm_device = device; + break; + } + } + + if (!drm_device) + drm_device = device; + else + udev_device_unref(device); + } + + udev_enumerate_unref(e); + return drm_device; +} + +static void +planes_binding(struct weston_seat *seat, uint32_t time, uint32_t key, void *data) +{ + struct drm_compositor *c = data; + + switch (key) { + case KEY_C: + c->cursors_are_broken ^= 1; + break; + case KEY_V: + c->sprites_are_broken ^= 1; + break; + case KEY_O: + c->sprites_hidden ^= 1; + break; + default: + break; + } +} + +#ifdef BUILD_VAAPI_RECORDER +static void +recorder_frame_notify(struct wl_listener *listener, void *data) +{ + struct drm_output *output; + struct drm_compositor *c; + int fd, ret; + + output = container_of(listener, struct drm_output, + recorder_frame_listener); + c = (struct drm_compositor *) output->base.compositor; + + if (!output->recorder) + return; + + ret = drmPrimeHandleToFD(c->drm.fd, output->current->handle, + DRM_CLOEXEC, &fd); + if (ret) { + weston_log("[libva recorder] " + "failed to create prime fd for front buffer\n"); + return; + } + + vaapi_recorder_frame(output->recorder, fd, output->current->stride / 4); +} + +static void * +create_recorder(struct drm_compositor *c, int width, int height, + const char *filename) +{ + int fd; + drm_magic_t magic; + + fd = open(c->drm.filename, O_RDWR | O_CLOEXEC); + if (fd < 0) + return NULL; + + drmGetMagic(fd, &magic); + drmAuthMagic(c->drm.fd, magic); + + return vaapi_recorder_create(fd, width, height, filename); +} + +static void +recorder_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + struct drm_compositor *c = data; + struct drm_output *output; + int width, height; + + output = container_of(c->base.output_list.next, + struct drm_output, base.link); + + if (!output->recorder) { + width = output->base.current_mode->width; + height = output->base.current_mode->height; + + output->recorder = + create_recorder(c, width, height, "capture.h264"); + if (!output->recorder) { + weston_log("failed to create vaapi recorder\n"); + return; + } + + output->base.disable_planes++; + + output->recorder_frame_listener.notify = recorder_frame_notify; + wl_signal_add(&output->base.frame_signal, + &output->recorder_frame_listener); + + weston_output_schedule_repaint(&output->base); + + weston_log("[libva recorder] initialized\n"); + } else { + vaapi_recorder_destroy(output->recorder); + output->recorder = NULL; + + output->base.disable_planes--; + + wl_list_remove(&output->recorder_frame_listener.link); + weston_log("[libva recorder] done\n"); + } +} +#else +static void +recorder_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + weston_log("Compiled without libva support\n"); +} +#endif + +static struct weston_compositor * +drm_compositor_create(struct wl_display *display, + int connector, const char *seat_id, int tty, int pixman, + int *argc, char *argv[], + struct weston_config *config) +{ + struct drm_compositor *ec; + struct udev_device *drm_device; + struct wl_event_loop *loop; + const char *path; + uint32_t key; + + weston_log("initializing drm backend\n"); + + ec = zalloc(sizeof *ec); + if (ec == NULL) + return NULL; + + /* KMS support for sprites is not complete yet, so disable the + * functionality for now. */ + ec->sprites_are_broken = 1; + ec->format = GBM_FORMAT_XRGB8888; + ec->use_pixman = pixman; + + if (weston_compositor_init(&ec->base, display, argc, argv, + config) < 0) { + weston_log("%s failed\n", __func__); + goto err_base; + } + + /* Check if we run drm-backend using weston-launch */ + ec->base.launcher = weston_launcher_connect(&ec->base, tty); + if (ec->base.launcher == NULL) { + weston_log("fatal: drm backend should be run " + "using weston-launch binary or as root\n"); + goto err_compositor; + } + + ec->udev = udev_new(); + if (ec->udev == NULL) { + weston_log("failed to initialize udev context\n"); + goto err_launcher; + } + + ec->base.wl_display = display; + ec->session_listener.notify = session_notify; + wl_signal_add(&ec->base.session_signal, &ec->session_listener); + + drm_device = find_primary_gpu(ec, seat_id); + if (drm_device == NULL) { + weston_log("no drm device found\n"); + goto err_udev; + } + path = udev_device_get_syspath(drm_device); + + if (init_drm(ec, drm_device) < 0) { + weston_log("failed to initialize kms\n"); + goto err_udev_dev; + } + + if (ec->use_pixman) { + if (init_pixman(ec) < 0) { + weston_log("failed to initialize pixman renderer\n"); + goto err_udev_dev; + } + } else { + if (init_egl(ec) < 0) { + weston_log("failed to initialize egl\n"); + goto err_udev_dev; + } + } + + ec->base.destroy = drm_destroy; + ec->base.restore = drm_restore; + + ec->base.focus = 1; + + ec->prev_state = WESTON_COMPOSITOR_ACTIVE; + + for (key = KEY_F1; key < KEY_F9; key++) + weston_compositor_add_key_binding(&ec->base, key, + MODIFIER_CTRL | MODIFIER_ALT, + switch_vt_binding, ec); + + wl_list_init(&ec->sprite_list); + create_sprites(ec); + + if (create_outputs(ec, connector, drm_device) < 0) { + weston_log("failed to create output for %s\n", path); + goto err_sprite; + } + + path = NULL; + + if (udev_input_init(&ec->input, &ec->base, ec->udev, seat_id) < 0) { + weston_log("failed to create input devices\n"); + goto err_sprite; + } + + loop = wl_display_get_event_loop(ec->base.wl_display); + ec->drm_source = + wl_event_loop_add_fd(loop, ec->drm.fd, + WL_EVENT_READABLE, on_drm_input, ec); + + ec->udev_monitor = udev_monitor_new_from_netlink(ec->udev, "udev"); + if (ec->udev_monitor == NULL) { + weston_log("failed to intialize udev monitor\n"); + goto err_drm_source; + } + udev_monitor_filter_add_match_subsystem_devtype(ec->udev_monitor, + "drm", NULL); + ec->udev_drm_source = + wl_event_loop_add_fd(loop, + udev_monitor_get_fd(ec->udev_monitor), + WL_EVENT_READABLE, udev_drm_event, ec); + + if (udev_monitor_enable_receiving(ec->udev_monitor) < 0) { + weston_log("failed to enable udev-monitor receiving\n"); + goto err_udev_monitor; + } + + udev_device_unref(drm_device); + + weston_compositor_add_debug_binding(&ec->base, KEY_O, + planes_binding, ec); + weston_compositor_add_debug_binding(&ec->base, KEY_C, + planes_binding, ec); + weston_compositor_add_debug_binding(&ec->base, KEY_V, + planes_binding, ec); + weston_compositor_add_debug_binding(&ec->base, KEY_Q, + recorder_binding, ec); + + return &ec->base; + +err_udev_monitor: + wl_event_source_remove(ec->udev_drm_source); + udev_monitor_unref(ec->udev_monitor); +err_drm_source: + wl_event_source_remove(ec->drm_source); + udev_input_destroy(&ec->input); +err_sprite: + ec->base.renderer->destroy(&ec->base); + gbm_device_destroy(ec->gbm); + destroy_sprites(ec); +err_udev_dev: + udev_device_unref(drm_device); +err_launcher: + weston_launcher_destroy(ec->base.launcher); +err_udev: + udev_unref(ec->udev); +err_compositor: + weston_compositor_shutdown(&ec->base); +err_base: + free(ec); + return NULL; +} + +WL_EXPORT struct weston_compositor * +backend_init(struct wl_display *display, int *argc, char *argv[], + struct weston_config *config) +{ + int connector = 0, tty = 0, use_pixman = 0; + const char *seat_id = default_seat; + + const struct weston_option drm_options[] = { + { WESTON_OPTION_INTEGER, "connector", 0, &connector }, + { WESTON_OPTION_STRING, "seat", 0, &seat_id }, + { WESTON_OPTION_INTEGER, "tty", 0, &tty }, + { WESTON_OPTION_BOOLEAN, "current-mode", 0, &option_current_mode }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &use_pixman }, + }; + + parse_options(drm_options, ARRAY_LENGTH(drm_options), argc, argv); + + return drm_compositor_create(display, connector, seat_id, tty, use_pixman, + argc, argv, config); +} diff --git a/src/compositor-fbdev.c b/src/compositor-fbdev.c new file mode 100644 index 00000000..24140eff --- /dev/null +++ b/src/compositor-fbdev.c @@ -0,0 +1,973 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2012 Raspberry Pi Foundation + * Copyright © 2013 Philip Withnall + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "compositor.h" +#include "launcher-util.h" +#include "pixman-renderer.h" +#include "udev-seat.h" +#include "gl-renderer.h" + +struct fbdev_compositor { + struct weston_compositor base; + uint32_t prev_state; + + struct udev *udev; + struct udev_input input; + int use_pixman; + struct wl_listener session_listener; +}; + +struct fbdev_screeninfo { + unsigned int x_resolution; /* pixels, visible area */ + unsigned int y_resolution; /* pixels, visible area */ + unsigned int width_mm; /* visible screen width in mm */ + unsigned int height_mm; /* visible screen height in mm */ + unsigned int bits_per_pixel; + + size_t buffer_length; /* length of frame buffer memory in bytes */ + size_t line_length; /* length of a line in bytes */ + char id[16]; /* screen identifier */ + + pixman_format_code_t pixel_format; /* frame buffer pixel format */ + unsigned int refresh_rate; /* Hertz */ +}; + +struct fbdev_output { + struct fbdev_compositor *compositor; + struct weston_output base; + + struct weston_mode mode; + struct wl_event_source *finish_frame_timer; + + /* Frame buffer details. */ + const char *device; /* ownership shared with fbdev_parameters */ + struct fbdev_screeninfo fb_info; + void *fb; /* length is fb_info.buffer_length */ + + /* pixman details. */ + pixman_image_t *hw_surface; + pixman_image_t *shadow_surface; + void *shadow_buf; + uint8_t depth; +}; + +struct fbdev_parameters { + int tty; + char *device; + int use_gl; +}; + +static const char default_seat[] = "seat0"; + +static inline struct fbdev_output * +to_fbdev_output(struct weston_output *base) +{ + return container_of(base, struct fbdev_output, base); +} + +static inline struct fbdev_compositor * +to_fbdev_compositor(struct weston_compositor *base) +{ + return container_of(base, struct fbdev_compositor, base); +} + +static void +fbdev_output_start_repaint_loop(struct weston_output *output) +{ + uint32_t msec; + struct timeval tv; + + gettimeofday(&tv, NULL); + msec = tv.tv_sec * 1000 + tv.tv_usec / 1000; + weston_output_finish_frame(output, msec); +} + +static void +fbdev_output_repaint_pixman(struct weston_output *base, pixman_region32_t *damage) +{ + struct fbdev_output *output = to_fbdev_output(base); + struct weston_compositor *ec = output->base.compositor; + pixman_box32_t *rects; + int nrects, i, src_x, src_y, x1, y1, x2, y2, width, height; + + /* Repaint the damaged region onto the back buffer. */ + pixman_renderer_output_set_buffer(base, output->shadow_surface); + ec->renderer->repaint_output(base, damage); + + /* Transform and composite onto the frame buffer. */ + width = pixman_image_get_width(output->shadow_surface); + height = pixman_image_get_height(output->shadow_surface); + rects = pixman_region32_rectangles(damage, &nrects); + + for (i = 0; i < nrects; i++) { + switch (base->transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + x1 = rects[i].x1; + x2 = rects[i].x2; + y1 = rects[i].y1; + y2 = rects[i].y2; + break; + case WL_OUTPUT_TRANSFORM_180: + x1 = width - rects[i].x2; + x2 = width - rects[i].x1; + y1 = height - rects[i].y2; + y2 = height - rects[i].y1; + break; + case WL_OUTPUT_TRANSFORM_90: + x1 = height - rects[i].y2; + x2 = height - rects[i].y1; + y1 = rects[i].x1; + y2 = rects[i].x2; + break; + case WL_OUTPUT_TRANSFORM_270: + x1 = rects[i].y1; + x2 = rects[i].y2; + y1 = width - rects[i].x2; + y2 = width - rects[i].x1; + break; + } + src_x = x1; + src_y = y1; + + pixman_image_composite32(PIXMAN_OP_SRC, + output->shadow_surface, /* src */ + NULL /* mask */, + output->hw_surface, /* dest */ + src_x, src_y, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + x1, y1, /* dest_x, dest_y */ + x2 - x1, /* width */ + y2 - y1 /* height */); + } + + /* Update the damage region. */ + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + + /* Schedule the end of the frame. We do not sync this to the frame + * buffer clock because users who want that should be using the DRM + * compositor. FBIO_WAITFORVSYNC blocks and FB_ACTIVATE_VBL requires + * panning, which is broken in most kernel drivers. + * + * Finish the frame synchronised to the specified refresh rate. The + * refresh rate is given in mHz and the interval in ms. */ + wl_event_source_timer_update(output->finish_frame_timer, + 1000000 / output->mode.refresh); +} + +static int +fbdev_output_repaint(struct weston_output *base, pixman_region32_t *damage) +{ + struct fbdev_output *output = to_fbdev_output(base); + struct fbdev_compositor *fbc = output->compositor; + struct weston_compositor *ec = & fbc->base; + + if (fbc->use_pixman) { + fbdev_output_repaint_pixman(base,damage); + } else { + ec->renderer->repaint_output(base, damage); + /* Update the damage region. */ + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + + wl_event_source_timer_update(output->finish_frame_timer, + 1000000 / output->mode.refresh); + } + + return 0; +} + +static int +finish_frame_handler(void *data) +{ + struct fbdev_output *output = data; + + fbdev_output_start_repaint_loop(&output->base); + + return 1; +} + +static pixman_format_code_t +calculate_pixman_format(struct fb_var_screeninfo *vinfo, + struct fb_fix_screeninfo *finfo) +{ + /* Calculate the pixman format supported by the frame buffer from the + * buffer's metadata. Return 0 if no known pixman format is supported + * (since this has depth 0 it's guaranteed to not conflict with any + * actual pixman format). + * + * Documentation on the vinfo and finfo structures: + * http://www.mjmwired.net/kernel/Documentation/fb/api.txt + * + * TODO: Try a bit harder to support other formats, including setting + * the preferred format in the hardware. */ + int type; + + weston_log("Calculating pixman format from:\n" + STAMP_SPACE " - type: %i (aux: %i)\n" + STAMP_SPACE " - visual: %i\n" + STAMP_SPACE " - bpp: %i (grayscale: %i)\n" + STAMP_SPACE " - red: offset: %i, length: %i, MSB: %i\n" + STAMP_SPACE " - green: offset: %i, length: %i, MSB: %i\n" + STAMP_SPACE " - blue: offset: %i, length: %i, MSB: %i\n" + STAMP_SPACE " - transp: offset: %i, length: %i, MSB: %i\n", + finfo->type, finfo->type_aux, finfo->visual, + vinfo->bits_per_pixel, vinfo->grayscale, + vinfo->red.offset, vinfo->red.length, vinfo->red.msb_right, + vinfo->green.offset, vinfo->green.length, + vinfo->green.msb_right, + vinfo->blue.offset, vinfo->blue.length, + vinfo->blue.msb_right, + vinfo->transp.offset, vinfo->transp.length, + vinfo->transp.msb_right); + + /* We only handle packed formats at the moment. */ + if (finfo->type != FB_TYPE_PACKED_PIXELS) + return 0; + + /* We only handle true-colour frame buffers at the moment. */ + switch(finfo->visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_DIRECTCOLOR: + if (vinfo->grayscale != 0) + return 0; + break; + default: + return 0; + } + + /* We only support formats with MSBs on the left. */ + if (vinfo->red.msb_right != 0 || vinfo->green.msb_right != 0 || + vinfo->blue.msb_right != 0) + return 0; + + /* Work out the format type from the offsets. We only support RGBA and + * ARGB at the moment. */ + type = PIXMAN_TYPE_OTHER; + + if ((vinfo->transp.offset >= vinfo->red.offset || + vinfo->transp.length == 0) && + vinfo->red.offset >= vinfo->green.offset && + vinfo->green.offset >= vinfo->blue.offset) + type = PIXMAN_TYPE_ARGB; + else if (vinfo->red.offset >= vinfo->green.offset && + vinfo->green.offset >= vinfo->blue.offset && + vinfo->blue.offset >= vinfo->transp.offset) + type = PIXMAN_TYPE_RGBA; + + if (type == PIXMAN_TYPE_OTHER) + return 0; + + /* Build the format. */ + return PIXMAN_FORMAT(vinfo->bits_per_pixel, type, + vinfo->transp.length, + vinfo->red.length, + vinfo->green.length, + vinfo->blue.length); +} + +static int +calculate_refresh_rate(struct fb_var_screeninfo *vinfo) +{ + uint64_t quot; + + /* Calculate monitor refresh rate. Default is 60 Hz. Units are mHz. */ + quot = (vinfo->upper_margin + vinfo->lower_margin + vinfo->yres); + quot *= (vinfo->left_margin + vinfo->right_margin + vinfo->xres); + quot *= vinfo->pixclock; + + if (quot > 0) { + uint64_t refresh_rate; + + refresh_rate = 1000000000000000LLU / quot; + if (refresh_rate > 200000) + refresh_rate = 200000; /* cap at 200 Hz */ + + return refresh_rate; + } + + return 60 * 1000; /* default to 60 Hz */ +} + +static int +fbdev_query_screen_info(struct fbdev_output *output, int fd, + struct fbdev_screeninfo *info) +{ + struct fb_var_screeninfo varinfo; + struct fb_fix_screeninfo fixinfo; + + /* Probe the device for screen information. */ + if (ioctl(fd, FBIOGET_FSCREENINFO, &fixinfo) < 0 || + ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { + return -1; + } + + /* Store the pertinent data. */ + info->x_resolution = varinfo.xres; + info->y_resolution = varinfo.yres; + info->width_mm = varinfo.width; + info->height_mm = varinfo.height; + info->bits_per_pixel = varinfo.bits_per_pixel; + + info->buffer_length = fixinfo.smem_len; + info->line_length = fixinfo.line_length; + strncpy(info->id, fixinfo.id, sizeof(info->id) / sizeof(*info->id)); + + info->pixel_format = calculate_pixman_format(&varinfo, &fixinfo); + info->refresh_rate = calculate_refresh_rate(&varinfo); + + if (info->pixel_format == 0) { + weston_log("Frame buffer uses an unsupported format.\n"); + return -1; + } + + return 1; +} + +static int +fbdev_set_screen_info(struct fbdev_output *output, int fd, + struct fbdev_screeninfo *info) +{ + struct fb_var_screeninfo varinfo; + + /* Grab the current screen information. */ + if (ioctl(fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { + return -1; + } + + /* Update the information. */ + varinfo.xres = info->x_resolution; + varinfo.yres = info->y_resolution; + varinfo.width = info->width_mm; + varinfo.height = info->height_mm; + varinfo.bits_per_pixel = info->bits_per_pixel; + + /* Try to set up an ARGB (x8r8g8b8) pixel format. */ + varinfo.grayscale = 0; + varinfo.transp.offset = 24; + varinfo.transp.length = 0; + varinfo.transp.msb_right = 0; + varinfo.red.offset = 16; + varinfo.red.length = 8; + varinfo.red.msb_right = 0; + varinfo.green.offset = 8; + varinfo.green.length = 8; + varinfo.green.msb_right = 0; + varinfo.blue.offset = 0; + varinfo.blue.length = 8; + varinfo.blue.msb_right = 0; + + /* Set the device's screen information. */ + if (ioctl(fd, FBIOPUT_VSCREENINFO, &varinfo) < 0) { + return -1; + } + + return 1; +} + +static void fbdev_frame_buffer_destroy(struct fbdev_output *output); + +/* Returns an FD for the frame buffer device. */ +static int +fbdev_frame_buffer_open(struct fbdev_output *output, const char *fb_dev, + struct fbdev_screeninfo *screen_info) +{ + int fd = -1; + + weston_log("Opening fbdev frame buffer.\n"); + + /* Open the frame buffer device. */ + fd = open(fb_dev, O_RDWR | O_CLOEXEC); + if (fd < 0) { + weston_log("Failed to open frame buffer device ‘%s’: %s\n", + fb_dev, strerror(errno)); + return -1; + } + + /* Grab the screen info. */ + if (fbdev_query_screen_info(output, fd, screen_info) < 0) { + weston_log("Failed to get frame buffer info: %s\n", + strerror(errno)); + + close(fd); + return -1; + } + + return fd; +} + +/* Closes the FD on success or failure. */ +static int +fbdev_frame_buffer_map(struct fbdev_output *output, int fd) +{ + int retval = -1; + + weston_log("Mapping fbdev frame buffer.\n"); + + /* Map the frame buffer. Write-only mode, since we don't want to read + * anything back (because it's slow). */ + output->fb = mmap(NULL, output->fb_info.buffer_length, + PROT_WRITE, MAP_SHARED, fd, 0); + if (output->fb == MAP_FAILED) { + weston_log("Failed to mmap frame buffer: %s\n", + strerror(errno)); + goto out_close; + } + + /* Create a pixman image to wrap the memory mapped frame buffer. */ + output->hw_surface = + pixman_image_create_bits(output->fb_info.pixel_format, + output->fb_info.x_resolution, + output->fb_info.y_resolution, + output->fb, + output->fb_info.line_length); + if (output->hw_surface == NULL) { + weston_log("Failed to create surface for frame buffer.\n"); + goto out_unmap; + } + + /* Success! */ + retval = 0; + +out_unmap: + if (retval != 0 && output->fb != NULL) + fbdev_frame_buffer_destroy(output); + +out_close: + if (fd >= 0) + close(fd); + + return retval; +} + +static void +fbdev_frame_buffer_destroy(struct fbdev_output *output) +{ + weston_log("Destroying fbdev frame buffer.\n"); + + if (munmap(output->fb, output->fb_info.buffer_length) < 0) + weston_log("Failed to munmap frame buffer: %s\n", + strerror(errno)); + + output->fb = NULL; +} + +static void fbdev_output_destroy(struct weston_output *base); +static void fbdev_output_disable(struct weston_output *base); + +static int +fbdev_output_create(struct fbdev_compositor *compositor, + const char *device) +{ + struct fbdev_output *output; + pixman_transform_t transform; + int fb_fd; + int shadow_width, shadow_height; + int width, height; + unsigned int bytes_per_pixel; + struct wl_event_loop *loop; + + weston_log("Creating fbdev output.\n"); + + output = calloc(1, sizeof *output); + if (!output) + return -1; + + output->compositor = compositor; + output->device = device; + + /* Create the frame buffer. */ + fb_fd = fbdev_frame_buffer_open(output, device, &output->fb_info); + if (fb_fd < 0) { + weston_log("Creating frame buffer failed.\n"); + goto out_free; + } + if (compositor->use_pixman) { + if (fbdev_frame_buffer_map(output, fb_fd) < 0) { + weston_log("Mapping frame buffer failed.\n"); + goto out_free; + } + } else { + close(fb_fd); + } + + output->base.start_repaint_loop = fbdev_output_start_repaint_loop; + output->base.repaint = fbdev_output_repaint; + output->base.destroy = fbdev_output_destroy; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = NULL; + + /* only one static mode in list */ + output->mode.flags = + WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + output->mode.width = output->fb_info.x_resolution; + output->mode.height = output->fb_info.y_resolution; + output->mode.refresh = output->fb_info.refresh_rate; + wl_list_init(&output->base.mode_list); + wl_list_insert(&output->base.mode_list, &output->mode.link); + + output->base.current_mode = &output->mode; + output->base.subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; + output->base.make = "unknown"; + output->base.model = output->fb_info.id; + + weston_output_init(&output->base, &compositor->base, + 0, 0, output->fb_info.width_mm, + output->fb_info.height_mm, + WL_OUTPUT_TRANSFORM_NORMAL, + 1); + + width = output->fb_info.x_resolution; + height = output->fb_info.y_resolution; + + pixman_transform_init_identity(&transform); + switch (output->base.transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + shadow_width = width; + shadow_height = height; + pixman_transform_rotate(&transform, + NULL, 0, 0); + pixman_transform_translate(&transform, NULL, + 0, 0); + break; + case WL_OUTPUT_TRANSFORM_180: + shadow_width = width; + shadow_height = height; + pixman_transform_rotate(&transform, + NULL, -pixman_fixed_1, 0); + pixman_transform_translate(NULL, &transform, + pixman_int_to_fixed(shadow_width), + pixman_int_to_fixed(shadow_height)); + break; + case WL_OUTPUT_TRANSFORM_270: + shadow_width = height; + shadow_height = width; + pixman_transform_rotate(&transform, + NULL, 0, pixman_fixed_1); + pixman_transform_translate(&transform, + NULL, + pixman_int_to_fixed(shadow_width), + 0); + break; + case WL_OUTPUT_TRANSFORM_90: + shadow_width = height; + shadow_height = width; + pixman_transform_rotate(&transform, + NULL, 0, -pixman_fixed_1); + pixman_transform_translate(&transform, + NULL, + 0, + pixman_int_to_fixed(shadow_height)); + break; + } + + bytes_per_pixel = output->fb_info.bits_per_pixel / 8; + + output->shadow_buf = malloc(width * height * bytes_per_pixel); + output->shadow_surface = + pixman_image_create_bits(output->fb_info.pixel_format, + shadow_width, shadow_height, + output->shadow_buf, + shadow_width * bytes_per_pixel); + if (output->shadow_buf == NULL || output->shadow_surface == NULL) { + weston_log("Failed to create surface for frame buffer.\n"); + goto out_hw_surface; + } + + /* No need in transform for normal output */ + if (output->base.transform != WL_OUTPUT_TRANSFORM_NORMAL) + pixman_image_set_transform(output->shadow_surface, &transform); + + if (compositor->use_pixman) { + if (pixman_renderer_output_create(&output->base) < 0) + goto out_shadow_surface; + } else { + setenv("HYBRIS_EGLPLATFORM", "wayland", 1); + if (gl_renderer_output_create(&output->base, + (EGLNativeWindowType)NULL) < 0) { + weston_log("gl_renderer_output_create failed.\n"); + goto out_shadow_surface; + } + } + + + loop = wl_display_get_event_loop(compositor->base.wl_display); + output->finish_frame_timer = + wl_event_loop_add_timer(loop, finish_frame_handler, output); + + wl_list_insert(compositor->base.output_list.prev, &output->base.link); + + weston_log("fbdev output %d×%d px\n", + output->mode.width, output->mode.height); + weston_log_continue(STAMP_SPACE "guessing %d Hz and 96 dpi\n", + output->mode.refresh / 1000); + + return 0; + +out_shadow_surface: + pixman_image_unref(output->shadow_surface); + output->shadow_surface = NULL; +out_hw_surface: + free(output->shadow_buf); + pixman_image_unref(output->hw_surface); + output->hw_surface = NULL; + weston_output_destroy(&output->base); + fbdev_frame_buffer_destroy(output); +out_free: + free(output); + + return -1; +} + +static void +fbdev_output_destroy(struct weston_output *base) +{ + struct fbdev_output *output = to_fbdev_output(base); + struct fbdev_compositor *compositor = output->compositor; + + weston_log("Destroying fbdev output.\n"); + + /* Close the frame buffer. */ + fbdev_output_disable(base); + + if (compositor->use_pixman) { + if (base->renderer_state != NULL) + pixman_renderer_output_destroy(base); + + if (output->shadow_surface != NULL) { + pixman_image_unref(output->shadow_surface); + output->shadow_surface = NULL; + } + + if (output->shadow_buf != NULL) { + free(output->shadow_buf); + output->shadow_buf = NULL; + } + } else { + gl_renderer_output_destroy(base); + } + + /* Remove the output. */ + wl_list_remove(&output->base.link); + weston_output_destroy(&output->base); + + free(output); +} + +/* strcmp()-style return values. */ +static int +compare_screen_info (const struct fbdev_screeninfo *a, + const struct fbdev_screeninfo *b) +{ + if (a->x_resolution == b->x_resolution && + a->y_resolution == b->y_resolution && + a->width_mm == b->width_mm && + a->height_mm == b->height_mm && + a->bits_per_pixel == b->bits_per_pixel && + a->pixel_format == b->pixel_format && + a->refresh_rate == b->refresh_rate) + return 0; + + return 1; +} + +static int +fbdev_output_reenable(struct fbdev_compositor *compositor, + struct weston_output *base) +{ + struct fbdev_output *output = to_fbdev_output(base); + struct fbdev_screeninfo new_screen_info; + int fb_fd; + const char *device; + + weston_log("Re-enabling fbdev output.\n"); + + /* Create the frame buffer. */ + fb_fd = fbdev_frame_buffer_open(output, output->device, + &new_screen_info); + if (fb_fd < 0) { + weston_log("Creating frame buffer failed.\n"); + goto err; + } + + /* Check whether the frame buffer details have changed since we were + * disabled. */ + if (compare_screen_info (&output->fb_info, &new_screen_info) != 0) { + /* Perform a mode-set to restore the old mode. */ + if (fbdev_set_screen_info(output, fb_fd, + &output->fb_info) < 0) { + weston_log("Failed to restore mode settings. " + "Attempting to re-open output anyway.\n"); + } + + close(fb_fd); + + /* Remove and re-add the output so that resources depending on + * the frame buffer X/Y resolution (such as the shadow buffer) + * are re-initialised. */ + device = output->device; + fbdev_output_destroy(base); + fbdev_output_create(compositor, device); + + return 0; + } + + /* Map the device if it has the same details as before. */ + if (compositor->use_pixman) { + if (fbdev_frame_buffer_map(output, fb_fd) < 0) { + weston_log("Mapping frame buffer failed.\n"); + goto err; + } + } + + return 0; + +err: + return -1; +} + +/* NOTE: This leaves output->fb_info populated, caching data so that if + * fbdev_output_reenable() is called again, it can determine whether a mode-set + * is needed. */ +static void +fbdev_output_disable(struct weston_output *base) +{ + struct fbdev_output *output = to_fbdev_output(base); + struct fbdev_compositor *compositor = output->compositor; + + weston_log("Disabling fbdev output.\n"); + + if ( ! compositor->use_pixman) return; + + if (output->hw_surface != NULL) { + pixman_image_unref(output->hw_surface); + output->hw_surface = NULL; + } + + fbdev_frame_buffer_destroy(output); +} + +static void +fbdev_compositor_destroy(struct weston_compositor *base) +{ + struct fbdev_compositor *compositor = to_fbdev_compositor(base); + + udev_input_destroy(&compositor->input); + + /* Destroy the output. */ + weston_compositor_shutdown(&compositor->base); + + /* Chain up. */ + compositor->base.renderer->destroy(&compositor->base); + weston_launcher_destroy(compositor->base.launcher); + + free(compositor); +} + +static void +session_notify(struct wl_listener *listener, void *data) +{ + struct fbdev_compositor *compositor = data; + struct weston_output *output; + + if (compositor->base.session_active) { + weston_log("entering VT\n"); + compositor->base.focus = 1; + compositor->base.state = compositor->prev_state; + + wl_list_for_each(output, &compositor->base.output_list, link) { + fbdev_output_reenable(compositor, output); + } + + weston_compositor_damage_all(&compositor->base); + + udev_input_enable(&compositor->input, compositor->udev); + } else { + weston_log("leaving VT\n"); + udev_input_disable(&compositor->input); + + wl_list_for_each(output, &compositor->base.output_list, link) { + fbdev_output_disable(output); + } + + compositor->base.focus = 0; + compositor->prev_state = compositor->base.state; + weston_compositor_offscreen(&compositor->base); + + /* If we have a repaint scheduled (from the idle handler), make + * sure we cancel that so we don't try to pageflip when we're + * vt switched away. The OFFSCREEN state will prevent + * further attemps at repainting. When we switch + * back, we schedule a repaint, which will process + * pending frame callbacks. */ + + wl_list_for_each(output, + &compositor->base.output_list, link) { + output->repaint_needed = 0; + } + }; +} + +static void +fbdev_restore(struct weston_compositor *compositor) +{ + weston_launcher_restore(compositor->launcher); +} + +static void +switch_vt_binding(struct weston_seat *seat, uint32_t time, uint32_t key, void *data) +{ + struct weston_compositor *compositor = data; + + weston_launcher_activate_vt(compositor->launcher, key - KEY_F1 + 1); +} + +static struct weston_compositor * +fbdev_compositor_create(struct wl_display *display, int *argc, char *argv[], + struct weston_config *config, + struct fbdev_parameters *param) +{ + struct fbdev_compositor *compositor; + const char *seat_id = default_seat; + uint32_t key; + + weston_log("initializing fbdev backend\n"); + + compositor = calloc(1, sizeof *compositor); + if (compositor == NULL) + return NULL; + + if (weston_compositor_init(&compositor->base, display, argc, argv, + config) < 0) + goto out_free; + + compositor->udev = udev_new(); + if (compositor->udev == NULL) { + weston_log("Failed to initialize udev context.\n"); + goto out_compositor; + } + + /* Set up the TTY. */ + compositor->session_listener.notify = session_notify; + wl_signal_add(&compositor->base.session_signal, + &compositor->session_listener); + compositor->base.launcher = + weston_launcher_connect(&compositor->base, param->tty); + if (!compositor->base.launcher) { + weston_log("fatal: fbdev backend should be run " + "using weston-launch binary or as root\n"); + goto out_udev; + } + + compositor->base.destroy = fbdev_compositor_destroy; + compositor->base.restore = fbdev_restore; + + compositor->base.focus = 1; + compositor->prev_state = WESTON_COMPOSITOR_ACTIVE; + compositor->use_pixman = !param->use_gl; + + for (key = KEY_F1; key < KEY_F9; key++) + weston_compositor_add_key_binding(&compositor->base, key, + MODIFIER_CTRL | MODIFIER_ALT, + switch_vt_binding, + compositor); + if (compositor->use_pixman) { + if (pixman_renderer_init(&compositor->base) < 0) + goto out_launcher; + } else { + if (gl_renderer_create(&compositor->base, EGL_DEFAULT_DISPLAY, + gl_renderer_opaque_attribs, NULL) < 0) { + weston_log("gl_renderer_create failed.\n"); + goto out_launcher; + } + } + + if (fbdev_output_create(compositor, param->device) < 0) + goto out_pixman; + + udev_input_init(&compositor->input, &compositor->base, compositor->udev, seat_id); + + return &compositor->base; + +out_pixman: + compositor->base.renderer->destroy(&compositor->base); + +out_launcher: + weston_launcher_destroy(compositor->base.launcher); + +out_udev: + udev_unref(compositor->udev); + +out_compositor: + weston_compositor_shutdown(&compositor->base); + +out_free: + free(compositor); + + return NULL; +} + +WL_EXPORT struct weston_compositor * +backend_init(struct wl_display *display, int *argc, char *argv[], + struct weston_config *config) +{ + /* TODO: Ideally, available frame buffers should be enumerated using + * udev, rather than passing a device node in as a parameter. */ + struct fbdev_parameters param = { + .tty = 0, /* default to current tty */ + .device = "/dev/fb0", /* default frame buffer */ + .use_gl = 0, + }; + + const struct weston_option fbdev_options[] = { + { WESTON_OPTION_INTEGER, "tty", 0, ¶m.tty }, + { WESTON_OPTION_STRING, "device", 0, ¶m.device }, + { WESTON_OPTION_BOOLEAN, "use-gl", 0, ¶m.use_gl }, + }; + + parse_options(fbdev_options, ARRAY_LENGTH(fbdev_options), argc, argv); + + return fbdev_compositor_create(display, argc, argv, config, ¶m); +} diff --git a/src/compositor-headless.c b/src/compositor-headless.c new file mode 100644 index 00000000..9d9f6dd3 --- /dev/null +++ b/src/compositor-headless.c @@ -0,0 +1,206 @@ +/* + * Copyright © 2010-2011 Benjamin Franzke + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include "compositor.h" + +struct headless_compositor { + struct weston_compositor base; + struct weston_seat fake_seat; +}; + +struct headless_output { + struct weston_output base; + struct weston_mode mode; + struct wl_event_source *finish_frame_timer; +}; + + +static void +headless_output_start_repaint_loop(struct weston_output *output) +{ + uint32_t msec; + struct timeval tv; + + gettimeofday(&tv, NULL); + msec = tv.tv_sec * 1000 + tv.tv_usec / 1000; + weston_output_finish_frame(output, msec); +} + +static int +finish_frame_handler(void *data) +{ + headless_output_start_repaint_loop(data); + + return 1; +} + +static int +headless_output_repaint(struct weston_output *output_base, + pixman_region32_t *damage) +{ + struct headless_output *output = (struct headless_output *) output_base; + struct weston_compositor *ec = output->base.compositor; + + ec->renderer->repaint_output(&output->base, damage); + + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + + wl_event_source_timer_update(output->finish_frame_timer, 16); + + return 0; +} + +static void +headless_output_destroy(struct weston_output *output_base) +{ + struct headless_output *output = (struct headless_output *) output_base; + + wl_event_source_remove(output->finish_frame_timer); + free(output); + + return; +} + +static int +headless_compositor_create_output(struct headless_compositor *c, + int width, int height) +{ + struct headless_output *output; + struct wl_event_loop *loop; + + output = zalloc(sizeof *output); + if (output == NULL) + return -1; + + output->mode.flags = + WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + output->mode.width = width; + output->mode.height = height; + output->mode.refresh = 60; + wl_list_init(&output->base.mode_list); + wl_list_insert(&output->base.mode_list, &output->mode.link); + + output->base.current_mode = &output->mode; + weston_output_init(&output->base, &c->base, 0, 0, width, height, + WL_OUTPUT_TRANSFORM_NORMAL, 1); + + output->base.make = "weston"; + output->base.model = "headless"; + + weston_output_move(&output->base, 0, 0); + + loop = wl_display_get_event_loop(c->base.wl_display); + output->finish_frame_timer = + wl_event_loop_add_timer(loop, finish_frame_handler, output); + + output->base.start_repaint_loop = headless_output_start_repaint_loop; + output->base.repaint = headless_output_repaint; + output->base.destroy = headless_output_destroy; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = NULL; + + wl_list_insert(c->base.output_list.prev, &output->base.link); + + return 0; +} + +static void +headless_restore(struct weston_compositor *ec) +{ +} + +static void +headless_destroy(struct weston_compositor *ec) +{ + struct headless_compositor *c = (struct headless_compositor *) ec; + + ec->renderer->destroy(ec); + + weston_seat_release(&c->fake_seat); + weston_compositor_shutdown(ec); + + free(ec); +} + +static struct weston_compositor * +headless_compositor_create(struct wl_display *display, + int width, int height, const char *display_name, + int *argc, char *argv[], + struct weston_config *config) +{ + struct headless_compositor *c; + + c = zalloc(sizeof *c); + if (c == NULL) + return NULL; + + if (weston_compositor_init(&c->base, display, argc, argv, config) < 0) + goto err_free; + + weston_seat_init(&c->fake_seat, &c->base, "default"); + + c->base.destroy = headless_destroy; + c->base.restore = headless_restore; + + if (headless_compositor_create_output(c, width, height) < 0) + goto err_compositor; + + if (noop_renderer_init(&c->base) < 0) + goto err_compositor; + + return &c->base; + +err_compositor: + weston_compositor_shutdown(&c->base); +err_free: + free(c); + return NULL; +} + +WL_EXPORT struct weston_compositor * +backend_init(struct wl_display *display, int *argc, char *argv[], + struct weston_config *config) +{ + int width = 1024, height = 640; + char *display_name = NULL; + + const struct weston_option headless_options[] = { + { WESTON_OPTION_INTEGER, "width", 0, &width }, + { WESTON_OPTION_INTEGER, "height", 0, &height }, + }; + + parse_options(headless_options, + ARRAY_LENGTH(headless_options), argc, argv); + + return headless_compositor_create(display, width, height, display_name, + argc, argv, config); +} diff --git a/src/compositor-rdp.c b/src/compositor-rdp.c new file mode 100644 index 00000000..8a302f85 --- /dev/null +++ b/src/compositor-rdp.c @@ -0,0 +1,1094 @@ +/* + * Copyright © 2013 Hardening + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compositor.h" +#include "pixman-renderer.h" + +#define MAX_FREERDP_FDS 32 +#define DEFAULT_AXIS_STEP_DISTANCE wl_fixed_from_int(10) + +struct rdp_compositor_config { + int width; + int height; + char *bind_address; + int port; + char *rdp_key; + char *server_cert; + char *server_key; + char *extra_modes; + int env_socket; +}; + +struct rdp_output; + +struct rdp_compositor { + struct weston_compositor base; + + freerdp_listener *listener; + struct wl_event_source *listener_events[MAX_FREERDP_FDS]; + struct rdp_output *output; + + char *server_cert; + char *server_key; + char *rdp_key; + int tls_enabled; +}; + +enum peer_item_flags { + RDP_PEER_ACTIVATED = (1 << 0), + RDP_PEER_OUTPUT_ENABLED = (1 << 1), +}; + +struct rdp_peers_item { + int flags; + freerdp_peer *peer; + struct weston_seat seat; + + struct wl_list link; +}; + +struct rdp_output { + struct weston_output base; + struct wl_event_source *finish_frame_timer; + pixman_image_t *shadow_surface; + + struct wl_list peers; +}; + +struct rdp_peer_context { + rdpContext _p; + + struct rdp_compositor *rdpCompositor; + struct wl_event_source *events[MAX_FREERDP_FDS]; + RFX_CONTEXT *rfx_context; + wStream *encode_stream; + RFX_RECT *rfx_rects; + NSC_CONTEXT *nsc_context; + + struct rdp_peers_item item; +}; +typedef struct rdp_peer_context RdpPeerContext; + +static void +rdp_compositor_config_init(struct rdp_compositor_config *config) { + config->width = 640; + config->height = 480; + config->bind_address = NULL; + config->port = 3389; + config->rdp_key = NULL; + config->server_cert = NULL; + config->server_key = NULL; + config->extra_modes = NULL; + config->env_socket = 0; +} + +static void +rdp_peer_refresh_rfx(pixman_region32_t *damage, pixman_image_t *image, freerdp_peer *peer) +{ + int width, height, nrects, i; + pixman_box32_t *region, *rects; + uint32_t *ptr; + RFX_RECT *rfxRect; + rdpUpdate *update = peer->update; + SURFACE_BITS_COMMAND *cmd = &update->surface_bits_command; + RdpPeerContext *context = (RdpPeerContext *)peer->context; + + Stream_Clear(context->encode_stream); + Stream_SetPosition(context->encode_stream, 0); + + width = (damage->extents.x2 - damage->extents.x1); + height = (damage->extents.y2 - damage->extents.y1); + + cmd->destLeft = damage->extents.x1; + cmd->destTop = damage->extents.y1; + cmd->destRight = damage->extents.x2; + cmd->destBottom = damage->extents.y2; + cmd->bpp = 32; + cmd->codecID = peer->settings->RemoteFxCodecId; + cmd->width = width; + cmd->height = height; + + ptr = pixman_image_get_data(image) + damage->extents.x1 + + damage->extents.y1 * (pixman_image_get_stride(image) / sizeof(uint32_t)); + + rects = pixman_region32_rectangles(damage, &nrects); + context->rfx_rects = realloc(context->rfx_rects, nrects * sizeof *rfxRect); + + for (i = 0; i < nrects; i++) { + region = &rects[i]; + rfxRect = &context->rfx_rects[i]; + + rfxRect->x = (region->x1 - damage->extents.x1); + rfxRect->y = (region->y1 - damage->extents.y1); + rfxRect->width = (region->x2 - region->x1); + rfxRect->height = (region->y2 - region->y1); + } + + rfx_compose_message(context->rfx_context, context->encode_stream, context->rfx_rects, nrects, + (BYTE *)ptr, width, height, + pixman_image_get_stride(image) + ); + + cmd->bitmapDataLength = Stream_GetPosition(context->encode_stream); + cmd->bitmapData = Stream_Buffer(context->encode_stream); + + update->SurfaceBits(update->context, cmd); +} + + +static void +rdp_peer_refresh_nsc(pixman_region32_t *damage, pixman_image_t *image, freerdp_peer *peer) +{ + int width, height; + uint32_t *ptr; + rdpUpdate *update = peer->update; + SURFACE_BITS_COMMAND *cmd = &update->surface_bits_command; + RdpPeerContext *context = (RdpPeerContext *)peer->context; + + Stream_Clear(context->encode_stream); + Stream_SetPosition(context->encode_stream, 0); + + width = (damage->extents.x2 - damage->extents.x1); + height = (damage->extents.y2 - damage->extents.y1); + + cmd->destLeft = damage->extents.x1; + cmd->destTop = damage->extents.y1; + cmd->destRight = damage->extents.x2; + cmd->destBottom = damage->extents.y2; + cmd->bpp = 32; + cmd->codecID = peer->settings->NSCodecId; + cmd->width = width; + cmd->height = height; + + ptr = pixman_image_get_data(image) + damage->extents.x1 + + damage->extents.y1 * (pixman_image_get_stride(image) / sizeof(uint32_t)); + + nsc_compose_message(context->nsc_context, context->encode_stream, (BYTE *)ptr, + cmd->width, cmd->height, + pixman_image_get_stride(image)); + cmd->bitmapDataLength = Stream_GetPosition(context->encode_stream); + cmd->bitmapData = Stream_Buffer(context->encode_stream); + update->SurfaceBits(update->context, cmd); +} + +static void +pixman_image_flipped_subrect(const pixman_box32_t *rect, pixman_image_t *img, BYTE *dest) { + int stride = pixman_image_get_stride(img); + int h; + int toCopy = (rect->x2 - rect->x1) * 4; + int height = (rect->y2 - rect->y1); + const BYTE *src = (const BYTE *)pixman_image_get_data(img); + src += ((rect->y2-1) * stride) + (rect->x1 * 4); + + for (h = 0; h < height; h++, src -= stride, dest += toCopy) + memcpy(dest, src, toCopy); +} + +static void +rdp_peer_refresh_raw(pixman_region32_t *region, pixman_image_t *image, freerdp_peer *peer) +{ + rdpUpdate *update = peer->update; + SURFACE_BITS_COMMAND *cmd = &update->surface_bits_command; + SURFACE_FRAME_MARKER *marker = &update->surface_frame_marker; + pixman_box32_t *rect, subrect; + int nrects, i; + int heightIncrement, remainingHeight, top; + + rect = pixman_region32_rectangles(region, &nrects); + if (!nrects) + return; + + marker->frameId++; + marker->frameAction = SURFACECMD_FRAMEACTION_BEGIN; + update->SurfaceFrameMarker(peer->context, marker); + + cmd->bpp = 32; + cmd->codecID = 0; + + for (i = 0; i < nrects; i++, rect++) { + /*weston_log("rect(%d,%d, %d,%d)\n", rect->x1, rect->y1, rect->x2, rect->y2);*/ + cmd->destLeft = rect->x1; + cmd->destRight = rect->x2; + cmd->width = rect->x2 - rect->x1; + + heightIncrement = peer->settings->MultifragMaxRequestSize / (16 + cmd->width * 4); + remainingHeight = rect->y2 - rect->y1; + top = rect->y1; + + subrect.x1 = rect->x1; + subrect.x2 = rect->x2; + + while (remainingHeight) { + cmd->height = (remainingHeight > heightIncrement) ? heightIncrement : remainingHeight; + cmd->destTop = top; + cmd->destBottom = top + cmd->height; + cmd->bitmapDataLength = cmd->width * cmd->height * 4; + cmd->bitmapData = (BYTE *)realloc(cmd->bitmapData, cmd->bitmapDataLength); + + subrect.y1 = top; + subrect.y2 = top + cmd->height; + pixman_image_flipped_subrect(&subrect, image, cmd->bitmapData); + + /*weston_log("* sending (%d,%d, %d,%d)\n", subrect.x1, subrect.y1, subrect.x2, subrect.y2); */ + update->SurfaceBits(peer->context, cmd); + + remainingHeight -= cmd->height; + top += cmd->height; + } + } + + marker->frameAction = SURFACECMD_FRAMEACTION_END; + update->SurfaceFrameMarker(peer->context, marker); +} + +static void +rdp_peer_refresh_region(pixman_region32_t *region, freerdp_peer *peer) +{ + RdpPeerContext *context = (RdpPeerContext *)peer->context; + struct rdp_output *output = context->rdpCompositor->output; + rdpSettings *settings = peer->settings; + + if (settings->RemoteFxCodec) + rdp_peer_refresh_rfx(region, output->shadow_surface, peer); + else if (settings->NSCodec) + rdp_peer_refresh_nsc(region, output->shadow_surface, peer); + else + rdp_peer_refresh_raw(region, output->shadow_surface, peer); +} + +static void +rdp_output_start_repaint_loop(struct weston_output *output) +{ + uint32_t msec; + struct timeval tv; + + gettimeofday(&tv, NULL); + msec = tv.tv_sec * 1000 + tv.tv_usec / 1000; + weston_output_finish_frame(output, msec); +} + +static int +rdp_output_repaint(struct weston_output *output_base, pixman_region32_t *damage) +{ + struct rdp_output *output = container_of(output_base, struct rdp_output, base); + struct weston_compositor *ec = output->base.compositor; + struct rdp_peers_item *outputPeer; + + pixman_renderer_output_set_buffer(output_base, output->shadow_surface); + ec->renderer->repaint_output(&output->base, damage); + + wl_list_for_each(outputPeer, &output->peers, link) { + if ((outputPeer->flags & RDP_PEER_ACTIVATED) && + (outputPeer->flags & RDP_PEER_OUTPUT_ENABLED)) + { + rdp_peer_refresh_region(damage, outputPeer->peer); + } + } + + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + + wl_event_source_timer_update(output->finish_frame_timer, 16); + return 0; +} + +static void +rdp_output_destroy(struct weston_output *output_base) +{ + struct rdp_output *output = (struct rdp_output *)output_base; + + wl_event_source_remove(output->finish_frame_timer); + free(output); +} + +static int +finish_frame_handler(void *data) +{ + rdp_output_start_repaint_loop(data); + + return 1; +} + + +static struct weston_mode * +find_matching_mode(struct weston_output *output, struct weston_mode *target) { + struct weston_mode *local; + + wl_list_for_each(local, &output->mode_list, link) { + if((local->width == target->width) && (local->height == target->height)) + return local; + } + return 0; +} + +static int +rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) { + struct rdp_output *rdpOutput = container_of(output, struct rdp_output, base); + struct rdp_peers_item *rdpPeer; + rdpSettings *settings; + pixman_image_t *new_shadow_buffer; + struct weston_mode *local_mode; + + local_mode = find_matching_mode(output, target_mode); + if(!local_mode) { + weston_log("mode %dx%d not available\n", target_mode->width, target_mode->height); + return -ENOENT; + } + + if(local_mode == output->current_mode) + return 0; + + output->current_mode->flags &= ~WL_OUTPUT_MODE_CURRENT; + + output->current_mode = local_mode; + output->current_mode->flags |= WL_OUTPUT_MODE_CURRENT; + + pixman_renderer_output_destroy(output); + pixman_renderer_output_create(output); + + new_shadow_buffer = pixman_image_create_bits(PIXMAN_x8r8g8b8, target_mode->width, + target_mode->height, 0, target_mode->width * 4); + pixman_image_composite32(PIXMAN_OP_SRC, rdpOutput->shadow_surface, 0, new_shadow_buffer, + 0, 0, 0, 0, 0, 0, target_mode->width, target_mode->height); + pixman_image_unref(rdpOutput->shadow_surface); + rdpOutput->shadow_surface = new_shadow_buffer; + + wl_list_for_each(rdpPeer, &rdpOutput->peers, link) { + settings = rdpPeer->peer->settings; + if(!settings->DesktopResize) { + /* too bad this peer does not support desktop resize */ + rdpPeer->peer->Close(rdpPeer->peer); + } else { + settings->DesktopWidth = target_mode->width; + settings->DesktopHeight = target_mode->height; + rdpPeer->peer->update->DesktopResize(rdpPeer->peer->context); + } + } + return 0; +} + +static int +parse_extra_modes(const char *modes_str, struct rdp_output *output) { + const char *startAt = modes_str; + const char *nextPos; + int w, h; + struct weston_mode *mode; + + while(startAt && *startAt) { + nextPos = strchr(startAt, 'x'); + if(!nextPos) + return -1; + + w = strtoul(startAt, NULL, 0); + startAt = nextPos + 1; + if(!*startAt) + return -1; + + h = strtoul(startAt, NULL, 0); + + if(!w || (w > 3000) || !h || (h > 3000)) + return -1; + mode = malloc(sizeof *mode); + if(!mode) + return -1; + + mode->width = w; + mode->height = h; + mode->refresh = 5; + mode->flags = 0; + wl_list_insert(&output->base.mode_list, &mode->link); + + startAt = strchr(startAt, ','); + if(startAt && *startAt == ',') + startAt++; + } + return 0; +} +static int +rdp_compositor_create_output(struct rdp_compositor *c, int width, int height, + const char *extraModes) +{ + struct rdp_output *output; + struct wl_event_loop *loop; + struct weston_mode *currentMode, *next; + + output = zalloc(sizeof *output); + if (output == NULL) + return -1; + + wl_list_init(&output->peers); + wl_list_init(&output->base.mode_list); + + currentMode = malloc(sizeof *currentMode); + if(!currentMode) + goto out_free_output; + currentMode->flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + currentMode->width = width; + currentMode->height = height; + currentMode->refresh = 5; + wl_list_insert(&output->base.mode_list, ¤tMode->link); + + if(parse_extra_modes(extraModes, output) < 0) { + weston_log("invalid extra modes\n"); + goto out_free_output_and_modes; + } + + output->base.current_mode = output->base.native_mode = currentMode; + weston_output_init(&output->base, &c->base, 0, 0, width, height, + WL_OUTPUT_TRANSFORM_NORMAL, 1); + + output->base.make = "weston"; + output->base.model = "rdp"; + output->shadow_surface = pixman_image_create_bits(PIXMAN_x8r8g8b8, + width, height, + NULL, + width * 4); + if (output->shadow_surface == NULL) { + weston_log("Failed to create surface for frame buffer.\n"); + goto out_output; + } + + if (pixman_renderer_output_create(&output->base) < 0) + goto out_shadow_surface; + + weston_output_move(&output->base, 0, 0); + + loop = wl_display_get_event_loop(c->base.wl_display); + output->finish_frame_timer = wl_event_loop_add_timer(loop, finish_frame_handler, output); + + output->base.start_repaint_loop = rdp_output_start_repaint_loop; + output->base.repaint = rdp_output_repaint; + output->base.destroy = rdp_output_destroy; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = rdp_switch_mode; + c->output = output; + + wl_list_insert(c->base.output_list.prev, &output->base.link); + return 0; + +out_shadow_surface: + pixman_image_unref(output->shadow_surface); +out_output: + weston_output_destroy(&output->base); +out_free_output_and_modes: + wl_list_for_each_safe(currentMode, next, &output->base.mode_list, link) + free(currentMode); +out_free_output: + free(output); + return -1; +} + +static void +rdp_restore(struct weston_compositor *ec) +{ +} + +static void +rdp_destroy(struct weston_compositor *ec) +{ + ec->renderer->destroy(ec); + weston_compositor_shutdown(ec); + + free(ec); +} + +static +int rdp_listener_activity(int fd, uint32_t mask, void *data) { + freerdp_listener* instance = (freerdp_listener *)data; + + if (!(mask & WL_EVENT_READABLE)) + return 0; + if (!instance->CheckFileDescriptor(instance)) + { + weston_log("failed to check FreeRDP file descriptor\n"); + return -1; + } + return 0; +} + +static +int rdp_implant_listener(struct rdp_compositor *c, freerdp_listener* instance) { + int i, fd; + int rcount = 0; + void* rfds[MAX_FREERDP_FDS]; + struct wl_event_loop *loop; + + if (!instance->GetFileDescriptor(instance, rfds, &rcount)) { + weston_log("Failed to get FreeRDP file descriptor\n"); + return -1; + } + + loop = wl_display_get_event_loop(c->base.wl_display); + for (i = 0; i < rcount; i++) { + fd = (int)(long)(rfds[i]); + c->listener_events[i] = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + rdp_listener_activity, instance); + } + + for( ; i < MAX_FREERDP_FDS; i++) + c->listener_events[i] = 0; + return 0; +} + + +static void +rdp_peer_context_new(freerdp_peer* client, RdpPeerContext* context) +{ + context->item.peer = client; + context->item.flags = RDP_PEER_OUTPUT_ENABLED; + + context->rfx_context = rfx_context_new(); + context->rfx_context->mode = RLGR3; + context->rfx_context->width = client->settings->DesktopWidth; + context->rfx_context->height = client->settings->DesktopHeight; + rfx_context_set_pixel_format(context->rfx_context, RDP_PIXEL_FORMAT_B8G8R8A8); + + context->nsc_context = nsc_context_new(); + nsc_context_set_pixel_format(context->nsc_context, RDP_PIXEL_FORMAT_B8G8R8A8); + + context->encode_stream = Stream_New(NULL, 65536); +} + +static void +rdp_peer_context_free(freerdp_peer* client, RdpPeerContext* context) +{ + int i; + if(!context) + return; + + wl_list_remove(&context->item.link); + for(i = 0; i < MAX_FREERDP_FDS; i++) { + if (context->events[i]) + wl_event_source_remove(context->events[i]); + } + + if(context->item.flags & RDP_PEER_ACTIVATED) + weston_seat_release(&context->item.seat); + Stream_Free(context->encode_stream, TRUE); + nsc_context_free(context->nsc_context); + rfx_context_free(context->rfx_context); + free(context->rfx_rects); +} + + +static int +rdp_client_activity(int fd, uint32_t mask, void *data) { + freerdp_peer* client = (freerdp_peer *)data; + + if (!client->CheckFileDescriptor(client)) { + weston_log("unable to checkDescriptor for %p\n", client); + goto out_clean; + } + return 0; + +out_clean: + freerdp_peer_context_free(client); + freerdp_peer_free(client); + return 0; +} + +static BOOL +xf_peer_capabilities(freerdp_peer* client) +{ + return TRUE; +} + + +struct rdp_to_xkb_keyboard_layout { + UINT32 rdpLayoutCode; + char *xkbLayout; +}; + +/* picked from http://technet.microsoft.com/en-us/library/cc766503(WS.10).aspx */ +static struct rdp_to_xkb_keyboard_layout rdp_keyboards[] = { + {0x00000406, "dk"}, + {0x00000407, "de"}, + {0x00000409, "us"}, + {0x0000040c, "fr"}, + {0x00000410, "it"}, + {0x00000813, "be"}, + {0x00000000, 0}, +}; + +/* taken from 2.2.7.1.6 Input Capability Set (TS_INPUT_CAPABILITYSET) */ +static char *rdp_keyboard_types[] = { + "", /* 0: unused */ + "", /* 1: IBM PC/XT or compatible (83-key) keyboard */ + "", /* 2: Olivetti "ICO" (102-key) keyboard */ + "", /* 3: IBM PC/AT (84-key) or similar keyboard */ + "pc102",/* 4: IBM enhanced (101- or 102-key) keyboard */ + "", /* 5: Nokia 1050 and similar keyboards */ + "", /* 6: Nokia 9140 and similar keyboards */ + "" /* 7: Japanese keyboard */ +}; + +static BOOL +xf_peer_post_connect(freerdp_peer* client) +{ + RdpPeerContext *peerCtx; + struct rdp_compositor *c; + struct rdp_output *output; + rdpSettings *settings; + rdpPointerUpdate *pointer; + struct xkb_context *xkbContext; + struct xkb_rule_names xkbRuleNames; + struct xkb_keymap *keymap; + int i; + pixman_box32_t box; + pixman_region32_t damage; + + + peerCtx = (RdpPeerContext *)client->context; + c = peerCtx->rdpCompositor; + output = c->output; + settings = client->settings; + + if (!settings->SurfaceCommandsEnabled) { + weston_log("client doesn't support required SurfaceCommands\n"); + return FALSE; + } + + if (output->base.width != (int)settings->DesktopWidth || + output->base.height != (int)settings->DesktopHeight) + { + struct weston_mode new_mode; + struct weston_mode *target_mode; + new_mode.width = (int)settings->DesktopWidth; + new_mode.height = (int)settings->DesktopHeight; + target_mode = find_matching_mode(&output->base, &new_mode); + if (!target_mode) { + weston_log("client mode not found\n"); + return FALSE; + } + weston_output_switch_mode(&output->base, target_mode, 1, WESTON_MODE_SWITCH_SET_NATIVE); + output->base.width = new_mode.width; + output->base.height = new_mode.height; + } + + weston_log("kbd_layout:%x kbd_type:%x kbd_subType:%x kbd_functionKeys:%x\n", + settings->KeyboardLayout, settings->KeyboardType, settings->KeyboardSubType, + settings->KeyboardFunctionKey); + + memset(&xkbRuleNames, 0, sizeof(xkbRuleNames)); + if(settings->KeyboardType <= 7) + xkbRuleNames.model = rdp_keyboard_types[settings->KeyboardType]; + for(i = 0; rdp_keyboards[i].xkbLayout; i++) { + if(rdp_keyboards[i].rdpLayoutCode == settings->KeyboardLayout) { + xkbRuleNames.layout = rdp_keyboards[i].xkbLayout; + break; + } + } + + keymap = NULL; + if(xkbRuleNames.layout) { + xkbContext = xkb_context_new(0); + if(!xkbContext) { + weston_log("unable to create a xkb_context\n"); + return FALSE; + } + + keymap = xkb_keymap_new_from_names(xkbContext, &xkbRuleNames, 0); + } + weston_seat_init_keyboard(&peerCtx->item.seat, keymap); + weston_seat_init_pointer(&peerCtx->item.seat); + + peerCtx->item.flags |= RDP_PEER_ACTIVATED; + + /* disable pointer on the client side */ + pointer = client->update->pointer; + pointer->pointer_system.type = SYSPTR_NULL; + pointer->PointerSystem(client->context, &pointer->pointer_system); + + /* sends a full refresh */ + box.x1 = 0; + box.y1 = 0; + box.x2 = output->base.width; + box.y2 = output->base.height; + pixman_region32_init_with_extents(&damage, &box); + + rdp_peer_refresh_region(&damage, client); + + pixman_region32_fini(&damage); + + return TRUE; +} + +static BOOL +xf_peer_activate(freerdp_peer *client) +{ + RdpPeerContext *context = (RdpPeerContext *)client->context; + rfx_context_reset(context->rfx_context); + return TRUE; +} + +static void +xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) { + wl_fixed_t wl_x, wl_y, axis; + RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + struct rdp_output *output; + uint32_t button = 0; + + if (flags & PTR_FLAGS_MOVE) { + output = peerContext->rdpCompositor->output; + if(x < output->base.width && y < output->base.height) { + wl_x = wl_fixed_from_int((int)x); + wl_y = wl_fixed_from_int((int)y); + notify_motion_absolute(&peerContext->item.seat, weston_compositor_get_time(), + wl_x, wl_y); + } + } + + if (flags & PTR_FLAGS_BUTTON1) + button = BTN_LEFT; + else if (flags & PTR_FLAGS_BUTTON2) + button = BTN_RIGHT; + else if (flags & PTR_FLAGS_BUTTON3) + button = BTN_MIDDLE; + + if(button) { + notify_button(&peerContext->item.seat, weston_compositor_get_time(), button, + (flags & PTR_FLAGS_DOWN) ? WL_POINTER_BUTTON_STATE_PRESSED : WL_POINTER_BUTTON_STATE_RELEASED + ); + } + + if (flags & PTR_FLAGS_WHEEL) { + /* DEFAULT_AXIS_STEP_DISTANCE is stolen from compositor-x11.c + * The RDP specs says the lower bits of flags contains the "the number of rotation + * units the mouse wheel was rotated". + * + * http://blogs.msdn.com/b/oldnewthing/archive/2013/01/23/10387366.aspx explains the 120 value + */ + axis = (DEFAULT_AXIS_STEP_DISTANCE * (flags & 0xff)) / 120; + if (flags & PTR_FLAGS_WHEEL_NEGATIVE) + axis = -axis; + + notify_axis(&peerContext->item.seat, weston_compositor_get_time(), + WL_POINTER_AXIS_VERTICAL_SCROLL, + axis); + } +} + +static void +xf_extendedMouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) { + wl_fixed_t wl_x, wl_y; + RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + struct rdp_output *output; + + output = peerContext->rdpCompositor->output; + if(x < output->base.width && y < output->base.height) { + wl_x = wl_fixed_from_int((int)x); + wl_y = wl_fixed_from_int((int)y); + notify_motion_absolute(&peerContext->item.seat, weston_compositor_get_time(), + wl_x, wl_y); + } +} + + +static void +xf_input_synchronize_event(rdpInput *input, UINT32 flags) +{ + freerdp_peer *client = input->context->peer; + RdpPeerContext *peerCtx = (RdpPeerContext *)input->context; + struct rdp_output *output = peerCtx->rdpCompositor->output; + pixman_box32_t box; + pixman_region32_t damage; + + /* sends a full refresh */ + box.x1 = 0; + box.y1 = 0; + box.x2 = output->base.width; + box.y2 = output->base.height; + pixman_region32_init_with_extents(&damage, &box); + + rdp_peer_refresh_region(&damage, client); + + pixman_region32_fini(&damage); +} + +extern DWORD KEYCODE_TO_VKCODE_EVDEV[]; +static uint32_t vk_to_keycode[256]; +static void +init_vk_translator(void) +{ + int i; + + memset(vk_to_keycode, 0, sizeof(vk_to_keycode)); + for(i = 0; i < 256; i++) + vk_to_keycode[KEYCODE_TO_VKCODE_EVDEV[i] & 0xff] = i-8; +} + +static void +xf_input_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) +{ + uint32_t scan_code, vk_code, full_code; + enum wl_keyboard_key_state keyState; + RdpPeerContext *peerContext = (RdpPeerContext *)input->context; + int notify = 0; + + if (flags & KBD_FLAGS_DOWN) { + keyState = WL_KEYBOARD_KEY_STATE_PRESSED; + notify = 1; + } else if (flags & KBD_FLAGS_RELEASE) { + keyState = WL_KEYBOARD_KEY_STATE_RELEASED; + notify = 1; + } + + if(notify) { + full_code = code; + if(flags & KBD_FLAGS_EXTENDED) + full_code |= KBD_FLAGS_EXTENDED; + + vk_code = GetVirtualKeyCodeFromVirtualScanCode(full_code, 4); + if(vk_code > 0xff) { + weston_log("invalid vk_code %x", vk_code); + return; + } + scan_code = vk_to_keycode[vk_code]; + + + /*weston_log("code=%x ext=%d vk_code=%x scan_code=%x\n", code, (flags & KBD_FLAGS_EXTENDED) ? 1 : 0, + vk_code, scan_code);*/ + notify_key(&peerContext->item.seat, weston_compositor_get_time(), + scan_code, keyState, STATE_UPDATE_AUTOMATIC); + } +} + +static void +xf_input_unicode_keyboard_event(rdpInput *input, UINT16 flags, UINT16 code) +{ + weston_log("Client sent a unicode keyboard event (flags:0x%X code:0x%X)\n", flags, code); +} + + +static void +xf_suppress_output(rdpContext *context, BYTE allow, RECTANGLE_16 *area) { + RdpPeerContext *peerContext = (RdpPeerContext *)context; + if(allow) + peerContext->item.flags |= RDP_PEER_OUTPUT_ENABLED; + else + peerContext->item.flags &= (~RDP_PEER_OUTPUT_ENABLED); +} + +static int +rdp_peer_init(freerdp_peer *client, struct rdp_compositor *c) +{ + int rcount = 0; + void *rfds[MAX_FREERDP_FDS]; + int i, fd; + struct wl_event_loop *loop; + rdpSettings *settings; + rdpInput *input; + RdpPeerContext *peerCtx; + char seat_name[32]; + + client->ContextSize = sizeof(RdpPeerContext); + client->ContextNew = (psPeerContextNew)rdp_peer_context_new; + client->ContextFree = (psPeerContextFree)rdp_peer_context_free; + freerdp_peer_context_new(client); + + peerCtx = (RdpPeerContext *) client->context; + peerCtx->rdpCompositor = c; + + settings = client->settings; + settings->RdpKeyFile = c->rdp_key; + if(c->tls_enabled) { + settings->CertificateFile = c->server_cert; + settings->PrivateKeyFile = c->server_key; + } else { + settings->TlsSecurity = FALSE; + } + + settings->NlaSecurity = FALSE; + + client->Capabilities = xf_peer_capabilities; + client->PostConnect = xf_peer_post_connect; + client->Activate = xf_peer_activate; + + client->update->SuppressOutput = xf_suppress_output; + + input = client->input; + input->SynchronizeEvent = xf_input_synchronize_event; + input->MouseEvent = xf_mouseEvent; + input->ExtendedMouseEvent = xf_extendedMouseEvent; + input->KeyboardEvent = xf_input_keyboard_event; + input->UnicodeKeyboardEvent = xf_input_unicode_keyboard_event; + + if (snprintf(seat_name, 32, "rdp:%d:%s", client->sockfd, client->hostname) >= 32) + seat_name[31] = '\0'; + + weston_seat_init(&peerCtx->item.seat, &c->base, seat_name); + + client->Initialize(client); + + if (!client->GetFileDescriptor(client, rfds, &rcount)) { + weston_log("unable to retrieve client fds\n"); + return -1; + } + + loop = wl_display_get_event_loop(c->base.wl_display); + for(i = 0; i < rcount; i++) { + fd = (int)(long)(rfds[i]); + + peerCtx->events[i] = wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + rdp_client_activity, client); + } + for ( ; i < MAX_FREERDP_FDS; i++) + peerCtx->events[i] = 0; + + wl_list_insert(&c->output->peers, &peerCtx->item.link); + return 0; +} + + +static void +rdp_incoming_peer(freerdp_listener *instance, freerdp_peer *client) +{ + struct rdp_compositor *c = (struct rdp_compositor *)instance->param4; + if (rdp_peer_init(client, c) < 0) + return; +} + +static struct weston_compositor * +rdp_compositor_create(struct wl_display *display, + struct rdp_compositor_config *config, + int *argc, char *argv[], struct weston_config *wconfig) +{ + struct rdp_compositor *c; + char *fd_str; + int fd; + + c = zalloc(sizeof *c); + if (c == NULL) + return NULL; + + if (weston_compositor_init(&c->base, display, argc, argv, wconfig) < 0) + goto err_free; + + c->base.destroy = rdp_destroy; + c->base.restore = rdp_restore; + c->rdp_key = config->rdp_key ? strdup(config->rdp_key) : NULL; + + /* activate TLS only if certificate/key are available */ + if(config->server_cert && config->server_key) { + weston_log("TLS support activated\n"); + c->server_cert = strdup(config->server_cert); + c->server_key = strdup(config->server_key); + if(!c->server_cert || !c->server_key) + goto err_free_strings; + c->tls_enabled = 1; + } + + if (pixman_renderer_init(&c->base) < 0) + goto err_compositor; + + if (rdp_compositor_create_output(c, config->width, config->height, config->extra_modes) < 0) + goto err_compositor; + + if(!config->env_socket) { + c->listener = freerdp_listener_new(); + c->listener->PeerAccepted = rdp_incoming_peer; + c->listener->param4 = c; + if(!c->listener->Open(c->listener, config->bind_address, config->port)) { + weston_log("unable to bind rdp socket\n"); + goto err_listener; + } + + if (rdp_implant_listener(c, c->listener) < 0) + goto err_compositor; + } else { + /* get the socket from RDP_FD var */ + fd_str = getenv("RDP_FD"); + if(!fd_str) { + weston_log("RDP_FD env variable not set"); + goto err_output; + } + + fd = strtoul(fd_str, NULL, 10); + if(rdp_peer_init(freerdp_peer_new(fd), c)) + goto err_output; + } + + return &c->base; + +err_listener: + freerdp_listener_free(c->listener); +err_output: + weston_output_destroy(&c->output->base); +err_compositor: + weston_compositor_shutdown(&c->base); +err_free_strings: + if(c->rdp_key) + free(c->rdp_key); + if(c->server_cert) + free(c->server_cert); + if(c->server_key) + free(c->server_key); +err_free: + free(c); + return NULL; +} + +WL_EXPORT struct weston_compositor * +backend_init(struct wl_display *display, int *argc, char *argv[], + struct weston_config *wconfig) +{ + struct rdp_compositor_config config; + rdp_compositor_config_init(&config); + int major, minor, revision; + + freerdp_get_version(&major, &minor, &revision); + weston_log("using FreeRDP version %d.%d.%d\n", major, minor, revision); + init_vk_translator(); + + const struct weston_option rdp_options[] = { + { WESTON_OPTION_BOOLEAN, "env-socket", 0, &config.env_socket }, + { WESTON_OPTION_INTEGER, "width", 0, &config.width }, + { WESTON_OPTION_INTEGER, "height", 0, &config.height }, + { WESTON_OPTION_STRING, "extra-modes", 0, &config.extra_modes }, + { WESTON_OPTION_STRING, "address", 0, &config.bind_address }, + { WESTON_OPTION_INTEGER, "port", 0, &config.port }, + { WESTON_OPTION_STRING, "rdp4-key", 0, &config.rdp_key }, + { WESTON_OPTION_STRING, "rdp-tls-cert", 0, &config.server_cert }, + { WESTON_OPTION_STRING, "rdp-tls-key", 0, &config.server_key } + }; + + parse_options(rdp_options, ARRAY_LENGTH(rdp_options), argc, argv); + return rdp_compositor_create(display, &config, argc, argv, wconfig); +} diff --git a/src/compositor-rpi.c b/src/compositor-rpi.c new file mode 100644 index 00000000..60926325 --- /dev/null +++ b/src/compositor-rpi.c @@ -0,0 +1,844 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * Copyright © 2012-2013 Raspberry Pi Foundation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef HAVE_BCM_HOST +# include +#else +# include "rpi-bcm-stubs.h" +#endif + +#include "compositor.h" +#include "rpi-renderer.h" +#include "evdev.h" +#include "launcher-util.h" + +#if 0 +#define DBG(...) \ + weston_log(__VA_ARGS__) +#else +#define DBG(...) do {} while (0) +#endif + +struct rpi_compositor; +struct rpi_output; + +struct rpi_flippipe { + int readfd; + int writefd; + struct wl_event_source *source; +}; + +struct rpi_output { + struct rpi_compositor *compositor; + struct weston_output base; + int single_buffer; + + struct weston_mode mode; + struct rpi_flippipe flippipe; + + DISPMANX_DISPLAY_HANDLE_T display; +}; + +struct rpi_seat { + struct weston_seat base; + struct wl_list devices_list; + + struct udev_monitor *udev_monitor; + struct wl_event_source *udev_monitor_source; + char *seat_id; +}; + +struct rpi_compositor { + struct weston_compositor base; + uint32_t prev_state; + + struct udev *udev; + struct wl_listener session_listener; + + int single_buffer; +}; + +static inline struct rpi_output * +to_rpi_output(struct weston_output *base) +{ + return container_of(base, struct rpi_output, base); +} + +static inline struct rpi_seat * +to_rpi_seat(struct weston_seat *base) +{ + return container_of(base, struct rpi_seat, base); +} + +static inline struct rpi_compositor * +to_rpi_compositor(struct weston_compositor *base) +{ + return container_of(base, struct rpi_compositor, base); +} + +static uint64_t +rpi_get_current_time(void) +{ + struct timeval tv; + + /* XXX: use CLOCK_MONOTONIC instead? */ + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +static void +rpi_flippipe_update_complete(DISPMANX_UPDATE_HANDLE_T update, void *data) +{ + /* This function runs in a different thread. */ + struct rpi_flippipe *flippipe = data; + uint64_t time; + ssize_t ret; + + /* manufacture flip completion timestamp */ + time = rpi_get_current_time(); + + ret = write(flippipe->writefd, &time, sizeof time); + if (ret != sizeof time) + weston_log("ERROR: %s failed to write, ret %zd, errno %d\n", + __func__, ret, errno); +} + +static int +rpi_dispmanx_update_submit(DISPMANX_UPDATE_HANDLE_T update, + struct rpi_output *output) +{ + /* + * The callback registered here will eventually be called + * in a different thread context. Therefore we cannot call + * the usual functions from rpi_flippipe_update_complete(). + * Instead, we have a pipe for passing the message from the + * thread, waking up the Weston main event loop, calling + * rpi_flippipe_handler(), and then ending up in + * rpi_output_update_complete() in the main thread context, + * where we can do the frame finishing work. + */ + return vc_dispmanx_update_submit(update, rpi_flippipe_update_complete, + &output->flippipe); +} + +static void +rpi_output_update_complete(struct rpi_output *output, uint64_t time); + +static int +rpi_flippipe_handler(int fd, uint32_t mask, void *data) +{ + struct rpi_output *output = data; + ssize_t ret; + uint64_t time; + + if (mask != WL_EVENT_READABLE) + weston_log("ERROR: unexpected mask 0x%x in %s\n", + mask, __func__); + + ret = read(fd, &time, sizeof time); + if (ret != sizeof time) { + weston_log("ERROR: %s failed to read, ret %zd, errno %d\n", + __func__, ret, errno); + } + + rpi_output_update_complete(output, time); + + return 1; +} + +static int +rpi_flippipe_init(struct rpi_flippipe *flippipe, struct rpi_output *output) +{ + struct wl_event_loop *loop; + int fd[2]; + + if (pipe2(fd, O_CLOEXEC) == -1) + return -1; + + flippipe->readfd = fd[0]; + flippipe->writefd = fd[1]; + + loop = wl_display_get_event_loop(output->compositor->base.wl_display); + flippipe->source = wl_event_loop_add_fd(loop, flippipe->readfd, + WL_EVENT_READABLE, + rpi_flippipe_handler, output); + + if (!flippipe->source) { + close(flippipe->readfd); + close(flippipe->writefd); + return -1; + } + + return 0; +} + +static void +rpi_flippipe_release(struct rpi_flippipe *flippipe) +{ + wl_event_source_remove(flippipe->source); + close(flippipe->readfd); + close(flippipe->writefd); +} + +static void +rpi_output_start_repaint_loop(struct weston_output *output) +{ + uint64_t time; + + time = rpi_get_current_time(); + weston_output_finish_frame(output, time); +} + +static int +rpi_output_repaint(struct weston_output *base, pixman_region32_t *damage) +{ + struct rpi_output *output = to_rpi_output(base); + struct rpi_compositor *compositor = output->compositor; + struct weston_plane *primary_plane = &compositor->base.primary_plane; + DISPMANX_UPDATE_HANDLE_T update; + + DBG("frame update start\n"); + + /* Update priority higher than in rpi-renderer's + * output destroy function, see rpi_output_destroy(). + */ + update = vc_dispmanx_update_start(1); + + rpi_renderer_set_update_handle(&output->base, update); + compositor->base.renderer->repaint_output(&output->base, damage); + + pixman_region32_subtract(&primary_plane->damage, + &primary_plane->damage, damage); + + /* schedule callback to rpi_output_update_complete() */ + rpi_dispmanx_update_submit(update, output); + DBG("frame update submitted\n"); + return 0; +} + +static void +rpi_output_update_complete(struct rpi_output *output, uint64_t time) +{ + DBG("frame update complete(%" PRIu64 ")\n", time); + rpi_renderer_finish_frame(&output->base); + weston_output_finish_frame(&output->base, time); +} + +static void +rpi_output_destroy(struct weston_output *base) +{ + struct rpi_output *output = to_rpi_output(base); + + DBG("%s\n", __func__); + + rpi_renderer_output_destroy(base); + + /* rpi_renderer_output_destroy() will schedule a removal of + * all Dispmanx Elements, and wait for the update to complete. + * Assuming updates are sequential, the wait should guarantee, + * that any pending rpi_flippipe_update_complete() callbacks + * have happened already. Therefore we can destroy the flippipe + * now. + */ + rpi_flippipe_release(&output->flippipe); + + wl_list_remove(&output->base.link); + weston_output_destroy(&output->base); + + vc_dispmanx_display_close(output->display); + + free(output); +} + +static const char *transform_names[] = { + [WL_OUTPUT_TRANSFORM_NORMAL] = "normal", + [WL_OUTPUT_TRANSFORM_90] = "90", + [WL_OUTPUT_TRANSFORM_180] = "180", + [WL_OUTPUT_TRANSFORM_270] = "270", + [WL_OUTPUT_TRANSFORM_FLIPPED] = "flipped", + [WL_OUTPUT_TRANSFORM_FLIPPED_90] = "flipped-90", + [WL_OUTPUT_TRANSFORM_FLIPPED_180] = "flipped-180", + [WL_OUTPUT_TRANSFORM_FLIPPED_270] = "flipped-270", +}; + +static int +str2transform(const char *name) +{ + unsigned i; + + for (i = 0; i < ARRAY_LENGTH(transform_names); i++) + if (strcmp(name, transform_names[i]) == 0) + return i; + + return -1; +} + +static const char * +transform2str(uint32_t output_transform) +{ + if (output_transform >= ARRAY_LENGTH(transform_names)) + return ""; + + return transform_names[output_transform]; +} + +static int +rpi_output_create(struct rpi_compositor *compositor, uint32_t transform) +{ + struct rpi_output *output; + DISPMANX_MODEINFO_T modeinfo; + int ret; + float mm_width, mm_height; + + output = calloc(1, sizeof *output); + if (!output) + return -1; + + output->compositor = compositor; + output->single_buffer = compositor->single_buffer; + + if (rpi_flippipe_init(&output->flippipe, output) < 0) { + weston_log("Creating message pipe failed.\n"); + goto out_free; + } + + output->display = vc_dispmanx_display_open(DISPMANX_ID_HDMI); + if (!output->display) { + weston_log("Failed to open dispmanx HDMI display.\n"); + goto out_pipe; + } + + ret = vc_dispmanx_display_get_info(output->display, &modeinfo); + if (ret < 0) { + weston_log("Failed to get display mode information.\n"); + goto out_dmx_close; + } + + output->base.start_repaint_loop = rpi_output_start_repaint_loop; + output->base.repaint = rpi_output_repaint; + output->base.destroy = rpi_output_destroy; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = NULL; + + /* XXX: use tvservice to get information from and control the + * HDMI and SDTV outputs. See: + * /opt/vc/include/interface/vmcs_host/vc_tvservice.h + */ + + /* only one static mode in list */ + output->mode.flags = + WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + output->mode.width = modeinfo.width; + output->mode.height = modeinfo.height; + output->mode.refresh = 60000; + wl_list_init(&output->base.mode_list); + wl_list_insert(&output->base.mode_list, &output->mode.link); + + output->base.current_mode = &output->mode; + output->base.subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; + output->base.make = "unknown"; + output->base.model = "unknown"; + + /* guess 96 dpi */ + mm_width = modeinfo.width * (25.4f / 96.0f); + mm_height = modeinfo.height * (25.4f / 96.0f); + + weston_output_init(&output->base, &compositor->base, + 0, 0, round(mm_width), round(mm_height), + transform, 1); + + if (rpi_renderer_output_create(&output->base, output->display) < 0) + goto out_output; + + wl_list_insert(compositor->base.output_list.prev, &output->base.link); + + weston_log("Raspberry Pi HDMI output %dx%d px\n", + output->mode.width, output->mode.height); + weston_log_continue(STAMP_SPACE "guessing %d Hz and 96 dpi\n", + output->mode.refresh / 1000); + weston_log_continue(STAMP_SPACE "orientation: %s\n", + transform2str(output->base.transform)); + + if (!strncmp(transform2str(output->base.transform), "flipped", 7)) + weston_log("warning: flipped output transforms may not work\n"); + + return 0; + +out_output: + weston_output_destroy(&output->base); + +out_dmx_close: + vc_dispmanx_display_close(output->display); + +out_pipe: + rpi_flippipe_release(&output->flippipe); + +out_free: + free(output); + return -1; +} + +static void +rpi_led_update(struct weston_seat *seat_base, enum weston_led leds) +{ + struct rpi_seat *seat = to_rpi_seat(seat_base); + struct evdev_device *device; + + wl_list_for_each(device, &seat->devices_list, link) + evdev_led_update(device, leds); +} + +static const char default_seat[] = "seat0"; + +static void +device_added(struct udev_device *udev_device, struct rpi_seat *master) +{ + struct evdev_device *device; + const char *devnode; + const char *device_seat; + int fd; + + device_seat = udev_device_get_property_value(udev_device, "ID_SEAT"); + if (!device_seat) + device_seat = default_seat; + + if (strcmp(device_seat, master->seat_id)) + return; + + devnode = udev_device_get_devnode(udev_device); + + /* Use non-blocking mode so that we can loop on read on + * evdev_device_data() until all events on the fd are + * read. mtdev_get() also expects this. */ + fd = open(devnode, O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (fd < 0) { + weston_log("opening input device '%s' failed.\n", devnode); + return; + } + + device = evdev_device_create(&master->base, devnode, fd); + if (!device) { + close(fd); + weston_log("not using input device '%s'.\n", devnode); + return; + } + + wl_list_insert(master->devices_list.prev, &device->link); +} + +static void +evdev_add_devices(struct udev *udev, struct weston_seat *seat_base) +{ + struct rpi_seat *seat = to_rpi_seat(seat_base); + struct udev_enumerate *e; + struct udev_list_entry *entry; + struct udev_device *device; + const char *path, *sysname; + + e = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(e, "input"); + udev_enumerate_scan_devices(e); + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { + path = udev_list_entry_get_name(entry); + device = udev_device_new_from_syspath(udev, path); + + sysname = udev_device_get_sysname(device); + if (strncmp("event", sysname, 5) != 0) { + udev_device_unref(device); + continue; + } + + device_added(device, seat); + + udev_device_unref(device); + } + udev_enumerate_unref(e); + + evdev_notify_keyboard_focus(&seat->base, &seat->devices_list); + + if (wl_list_empty(&seat->devices_list)) { + weston_log( + "warning: no input devices on entering Weston. " + "Possible causes:\n" + "\t- no permissions to read /dev/input/event*\n" + "\t- seats misconfigured " + "(Weston backend option 'seat', " + "udev device property ID_SEAT)\n"); + } +} + +static int +evdev_udev_handler(int fd, uint32_t mask, void *data) +{ + struct rpi_seat *seat = data; + struct udev_device *udev_device; + struct evdev_device *device, *next; + const char *action; + const char *devnode; + + udev_device = udev_monitor_receive_device(seat->udev_monitor); + if (!udev_device) + return 1; + + action = udev_device_get_action(udev_device); + if (!action) + goto out; + + if (strncmp("event", udev_device_get_sysname(udev_device), 5) != 0) + goto out; + + if (!strcmp(action, "add")) { + device_added(udev_device, seat); + } + else if (!strcmp(action, "remove")) { + devnode = udev_device_get_devnode(udev_device); + wl_list_for_each_safe(device, next, &seat->devices_list, link) + if (!strcmp(device->devnode, devnode)) { + weston_log("input device %s, %s removed\n", + device->devname, device->devnode); + evdev_device_destroy(device); + break; + } + } + +out: + udev_device_unref(udev_device); + + return 0; +} + +static int +evdev_enable_udev_monitor(struct udev *udev, struct weston_seat *seat_base) +{ + struct rpi_seat *master = to_rpi_seat(seat_base); + struct wl_event_loop *loop; + struct weston_compositor *c = master->base.compositor; + int fd; + + master->udev_monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (!master->udev_monitor) { + weston_log("udev: failed to create the udev monitor\n"); + return 0; + } + + udev_monitor_filter_add_match_subsystem_devtype(master->udev_monitor, + "input", NULL); + + if (udev_monitor_enable_receiving(master->udev_monitor)) { + weston_log("udev: failed to bind the udev monitor\n"); + udev_monitor_unref(master->udev_monitor); + return 0; + } + + loop = wl_display_get_event_loop(c->wl_display); + fd = udev_monitor_get_fd(master->udev_monitor); + master->udev_monitor_source = + wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + evdev_udev_handler, master); + if (!master->udev_monitor_source) { + udev_monitor_unref(master->udev_monitor); + return 0; + } + + return 1; +} + +static void +evdev_disable_udev_monitor(struct weston_seat *seat_base) +{ + struct rpi_seat *seat = to_rpi_seat(seat_base); + + if (!seat->udev_monitor) + return; + + udev_monitor_unref(seat->udev_monitor); + seat->udev_monitor = NULL; + wl_event_source_remove(seat->udev_monitor_source); + seat->udev_monitor_source = NULL; +} + +static void +evdev_input_create(struct weston_compositor *c, struct udev *udev, + const char *seat_id) +{ + struct rpi_seat *seat; + + seat = zalloc(sizeof *seat); + if (seat == NULL) + return; + + weston_seat_init(&seat->base, c, "default"); + seat->base.led_update = rpi_led_update; + + wl_list_init(&seat->devices_list); + seat->seat_id = strdup(seat_id); + if (!evdev_enable_udev_monitor(udev, &seat->base)) { + free(seat->seat_id); + free(seat); + return; + } + + evdev_add_devices(udev, &seat->base); +} + +static void +evdev_remove_devices(struct weston_seat *seat_base) +{ + struct rpi_seat *seat = to_rpi_seat(seat_base); + struct evdev_device *device, *next; + + wl_list_for_each_safe(device, next, &seat->devices_list, link) + evdev_device_destroy(device); + + if (seat->base.keyboard) + notify_keyboard_focus_out(&seat->base); +} + +static void +evdev_input_destroy(struct weston_seat *seat_base) +{ + struct rpi_seat *seat = to_rpi_seat(seat_base); + + evdev_remove_devices(seat_base); + evdev_disable_udev_monitor(&seat->base); + + weston_seat_release(seat_base); + free(seat->seat_id); + free(seat); +} + +static void +rpi_compositor_destroy(struct weston_compositor *base) +{ + struct rpi_compositor *compositor = to_rpi_compositor(base); + struct weston_seat *seat, *next; + + wl_list_for_each_safe(seat, next, &compositor->base.seat_list, link) + evdev_input_destroy(seat); + + /* destroys outputs, too */ + weston_compositor_shutdown(&compositor->base); + + compositor->base.renderer->destroy(&compositor->base); + weston_launcher_destroy(compositor->base.launcher); + + bcm_host_deinit(); + free(compositor); +} + +static void +session_notify(struct wl_listener *listener, void *data) +{ + struct rpi_compositor *compositor = data; + struct weston_seat *seat; + struct weston_output *output; + + if (compositor->base.session_active) { + weston_log("activating session\n"); + compositor->base.focus = 1; + compositor->base.state = compositor->prev_state; + weston_compositor_damage_all(&compositor->base); + wl_list_for_each(seat, &compositor->base.seat_list, link) { + evdev_add_devices(compositor->udev, seat); + evdev_enable_udev_monitor(compositor->udev, seat); + } + } else { + weston_log("deactivating session\n"); + wl_list_for_each(seat, &compositor->base.seat_list, link) { + evdev_disable_udev_monitor(seat); + evdev_remove_devices(seat); + } + + compositor->base.focus = 0; + compositor->prev_state = compositor->base.state; + weston_compositor_offscreen(&compositor->base); + + /* If we have a repaint scheduled (either from a + * pending pageflip or the idle handler), make sure we + * cancel that so we don't try to pageflip when we're + * vt switched away. The OFFSCREEN state will prevent + * further attemps at repainting. When we switch + * back, we schedule a repaint, which will process + * pending frame callbacks. */ + + wl_list_for_each(output, + &compositor->base.output_list, link) { + output->repaint_needed = 0; + } + }; +} + +static void +rpi_restore(struct weston_compositor *compositor) +{ + weston_launcher_restore(compositor->launcher); +} + +static void +switch_vt_binding(struct weston_seat *seat, uint32_t time, uint32_t key, void *data) +{ + struct weston_compositor *compositor = data; + + weston_launcher_activate_vt(compositor->launcher, key - KEY_F1 + 1); +} + +struct rpi_parameters { + int tty; + struct rpi_renderer_parameters renderer; + uint32_t output_transform; +}; + +static struct weston_compositor * +rpi_compositor_create(struct wl_display *display, int *argc, char *argv[], + struct weston_config *config, + struct rpi_parameters *param) +{ + struct rpi_compositor *compositor; + const char *seat = default_seat; + uint32_t key; + + weston_log("initializing Raspberry Pi backend\n"); + + compositor = calloc(1, sizeof *compositor); + if (compositor == NULL) + return NULL; + + if (weston_compositor_init(&compositor->base, display, argc, argv, + config) < 0) + goto out_free; + + compositor->udev = udev_new(); + if (compositor->udev == NULL) { + weston_log("Failed to initialize udev context.\n"); + goto out_compositor; + } + + compositor->session_listener.notify = session_notify; + wl_signal_add(&compositor->base.session_signal, + &compositor ->session_listener); + compositor->base.launcher = + weston_launcher_connect(&compositor->base, param->tty); + if (!compositor->base.launcher) { + weston_log("Failed to initialize tty.\n"); + goto out_udev; + } + + compositor->base.destroy = rpi_compositor_destroy; + compositor->base.restore = rpi_restore; + + compositor->base.focus = 1; + compositor->prev_state = WESTON_COMPOSITOR_ACTIVE; + compositor->single_buffer = param->renderer.single_buffer; + + weston_log("Dispmanx planes are %s buffered.\n", + compositor->single_buffer ? "single" : "double"); + + for (key = KEY_F1; key < KEY_F9; key++) + weston_compositor_add_key_binding(&compositor->base, key, + MODIFIER_CTRL | MODIFIER_ALT, + switch_vt_binding, compositor); + + /* + * bcm_host_init() creates threads. + * Therefore we must have all signal handlers set and signals blocked + * before calling it. Otherwise the signals may end in the bcm + * threads and cause the default behaviour there. For instance, + * SIGUSR1 used for VT switching caused Weston to terminate there. + */ + bcm_host_init(); + + if (rpi_renderer_create(&compositor->base, ¶m->renderer) < 0) + goto out_launcher; + + if (rpi_output_create(compositor, param->output_transform) < 0) + goto out_renderer; + + evdev_input_create(&compositor->base, compositor->udev, seat); + + return &compositor->base; + +out_renderer: + compositor->base.renderer->destroy(&compositor->base); + +out_launcher: + weston_launcher_destroy(compositor->base.launcher); + +out_udev: + udev_unref(compositor->udev); + +out_compositor: + weston_compositor_shutdown(&compositor->base); + +out_free: + bcm_host_deinit(); + free(compositor); + + return NULL; +} + +WL_EXPORT struct weston_compositor * +backend_init(struct wl_display *display, int *argc, char *argv[], + struct weston_config *config) +{ + const char *transform = "normal"; + int ret; + + struct rpi_parameters param = { + .tty = 0, /* default to current tty */ + .renderer.single_buffer = 0, + .output_transform = WL_OUTPUT_TRANSFORM_NORMAL, + }; + + const struct weston_option rpi_options[] = { + { WESTON_OPTION_INTEGER, "tty", 0, ¶m.tty }, + { WESTON_OPTION_BOOLEAN, "single-buffer", 0, + ¶m.renderer.single_buffer }, + { WESTON_OPTION_STRING, "transform", 0, &transform }, + }; + + parse_options(rpi_options, ARRAY_LENGTH(rpi_options), argc, argv); + + ret = str2transform(transform); + if (ret < 0) + weston_log("invalid transform \"%s\"\n", transform); + else + param.output_transform = ret; + + return rpi_compositor_create(display, argc, argv, config, ¶m); +} diff --git a/src/compositor-wayland.c b/src/compositor-wayland.c new file mode 100644 index 00000000..f3b03619 --- /dev/null +++ b/src/compositor-wayland.c @@ -0,0 +1,808 @@ +/* + * Copyright © 2010-2011 Benjamin Franzke + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "compositor.h" +#include "gl-renderer.h" +#include "../shared/image-loader.h" +#include "../shared/os-compatibility.h" + +struct wayland_compositor { + struct weston_compositor base; + + struct { + struct wl_display *wl_display; + struct wl_registry *registry; + struct wl_compositor *compositor; + struct wl_shell *shell; + struct wl_output *output; + struct wl_shm *shm; + + struct { + int32_t x, y, width, height; + } screen_allocation; + + struct wl_event_source *wl_source; + uint32_t event_mask; + } parent; + + struct { + int32_t top, bottom, left, right; + } border; + + struct wl_list input_list; +}; + +struct wayland_output { + struct weston_output base; + struct { + int draw_initial_frame; + struct wl_surface *surface; + struct wl_shell_surface *shell_surface; + struct wl_egl_window *egl_window; + } parent; + struct weston_mode mode; +}; + +struct wayland_input { + struct weston_seat base; + struct wayland_compositor *compositor; + struct wl_seat *seat; + struct wl_pointer *pointer; + struct wl_keyboard *keyboard; + struct wl_touch *touch; + struct wl_list link; + uint32_t key_serial; + uint32_t enter_serial; + int focus; + struct wayland_output *output; +}; + +static void +create_border(struct wayland_compositor *c) +{ + pixman_image_t *image; + int32_t edges[4]; + + image = load_image(DATADIR "/weston/border.png"); + if (!image) { + weston_log("couldn't load border image\n"); + return; + } + + edges[0] = c->border.left; + edges[1] = c->border.right; + edges[2] = c->border.top; + edges[3] = c->border.bottom; + + gl_renderer_set_border(&c->base, pixman_image_get_width(image), + pixman_image_get_height(image), + pixman_image_get_data(image), edges); + + pixman_image_unref(image); +} + +static void +frame_done(void *data, struct wl_callback *callback, uint32_t time) +{ + struct weston_output *output = data; + + wl_callback_destroy(callback); + weston_output_finish_frame(output, time); +} + +static const struct wl_callback_listener frame_listener = { + frame_done +}; + +static void +buffer_release(void *data, struct wl_buffer *buffer) +{ + wl_buffer_destroy(buffer); +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static void +draw_initial_frame(struct wayland_output *output) +{ + struct wayland_compositor *c = + (struct wayland_compositor *) output->base.compositor; + struct wl_shm *shm = c->parent.shm; + struct wl_surface *surface = output->parent.surface; + struct wl_shm_pool *pool; + struct wl_buffer *buffer; + + int width, height, stride; + int size; + int fd; + void *data; + + width = output->mode.width + c->border.left + c->border.right; + height = output->mode.height + c->border.top + c->border.bottom; + stride = width * 4; + size = height * stride; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + perror("os_create_anonymous_file"); + return; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + perror("mmap"); + close(fd); + return; + } + + pool = wl_shm_create_pool(shm, fd, size); + + buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, + stride, + WL_SHM_FORMAT_ARGB8888); + wl_buffer_add_listener(buffer, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + memset(data, 0, size); + + wl_surface_attach(surface, buffer, 0, 0); + + /* We only need to damage some part, as its only transparant + * pixels anyway. */ + wl_surface_damage(surface, 0, 0, 1, 1); +} + +static void +wayland_output_start_repaint_loop(struct weston_output *output_base) +{ + struct wayland_output *output = (struct wayland_output *) output_base; + struct wl_callback *callback; + + /* If this is the initial frame, we need to attach a buffer so that + * the compositor can map the surface and include it in its render + * loop. If the surface doesn't end up in the render loop, the frame + * callback won't be invoked. The buffer is transparent and of the + * same size as the future real output buffer. */ + if (output->parent.draw_initial_frame) { + output->parent.draw_initial_frame = 0; + + draw_initial_frame(output); + } + + callback = wl_surface_frame(output->parent.surface); + wl_callback_add_listener(callback, &frame_listener, output); + wl_surface_commit(output->parent.surface); +} + +static int +wayland_output_repaint(struct weston_output *output_base, + pixman_region32_t *damage) +{ + struct wayland_output *output = (struct wayland_output *) output_base; + struct weston_compositor *ec = output->base.compositor; + struct wl_callback *callback; + + callback = wl_surface_frame(output->parent.surface); + wl_callback_add_listener(callback, &frame_listener, output); + + ec->renderer->repaint_output(&output->base, damage); + + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + return 0; +} + +static void +wayland_output_destroy(struct weston_output *output_base) +{ + struct wayland_output *output = (struct wayland_output *) output_base; + + gl_renderer_output_destroy(output_base); + + wl_egl_window_destroy(output->parent.egl_window); + free(output); + + return; +} + +static const struct wl_shell_surface_listener shell_surface_listener; + +static int +wayland_compositor_create_output(struct wayland_compositor *c, + int width, int height) +{ + struct wayland_output *output; + + output = zalloc(sizeof *output); + if (output == NULL) + return -1; + + output->mode.flags = + WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + output->mode.width = width; + output->mode.height = height; + output->mode.refresh = 60; + wl_list_init(&output->base.mode_list); + wl_list_insert(&output->base.mode_list, &output->mode.link); + + output->base.current_mode = &output->mode; + weston_output_init(&output->base, &c->base, 0, 0, width, height, + WL_OUTPUT_TRANSFORM_NORMAL, 1); + + output->base.make = "waywayland"; + output->base.model = "none"; + + weston_output_move(&output->base, 0, 0); + + output->parent.surface = + wl_compositor_create_surface(c->parent.compositor); + wl_surface_set_user_data(output->parent.surface, output); + + output->parent.egl_window = + wl_egl_window_create(output->parent.surface, + width + c->border.left + c->border.right, + height + c->border.top + c->border.bottom); + if (!output->parent.egl_window) { + weston_log("failure to create wl_egl_window\n"); + goto cleanup_output; + } + + if (gl_renderer_output_create(&output->base, + output->parent.egl_window) < 0) + goto cleanup_window; + + output->parent.draw_initial_frame = 1; + output->parent.shell_surface = + wl_shell_get_shell_surface(c->parent.shell, + output->parent.surface); + wl_shell_surface_add_listener(output->parent.shell_surface, + &shell_surface_listener, output); + wl_shell_surface_set_toplevel(output->parent.shell_surface); + + output->base.start_repaint_loop = wayland_output_start_repaint_loop; + output->base.repaint = wayland_output_repaint; + output->base.destroy = wayland_output_destroy; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = NULL; + + wl_list_insert(c->base.output_list.prev, &output->base.link); + + return 0; + +cleanup_window: + wl_egl_window_destroy(output->parent.egl_window); +cleanup_output: + /* FIXME: cleanup weston_output */ + free(output); + + return -1; +} + +static void +shell_surface_ping(void *data, struct wl_shell_surface *shell_surface, + uint32_t serial) +{ + wl_shell_surface_pong(shell_surface, serial); +} + +static void +shell_surface_configure(void *data, struct wl_shell_surface *shell_surface, + uint32_t edges, int32_t width, int32_t height) +{ + /* FIXME: implement resizing */ +} + +static void +shell_surface_popup_done(void *data, struct wl_shell_surface *shell_surface) +{ +} + +static const struct wl_shell_surface_listener shell_surface_listener = { + shell_surface_ping, + shell_surface_configure, + shell_surface_popup_done +}; + +/* Events received from the wayland-server this compositor is client of: */ + +/* parent output interface */ +static void +display_handle_geometry(void *data, + struct wl_output *wl_output, + int x, + int y, + int physical_width, + int physical_height, + int subpixel, + const char *make, + const char *model, + int transform) +{ + struct wayland_compositor *c = data; + + c->parent.screen_allocation.x = x; + c->parent.screen_allocation.y = y; +} + +static void +display_handle_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int width, + int height, + int refresh) +{ + struct wayland_compositor *c = data; + + c->parent.screen_allocation.width = width; + c->parent.screen_allocation.height = height; +} + +static const struct wl_output_listener output_listener = { + display_handle_geometry, + display_handle_mode +}; + +static void +check_focus(struct wayland_input *input, wl_fixed_t x, wl_fixed_t y) +{ + struct wayland_compositor *c = input->compositor; + int width, height, inside; + + width = input->output->mode.width; + height = input->output->mode.height; + + inside = c->border.left <= wl_fixed_to_int(x) && + wl_fixed_to_int(x) < width + c->border.left && + c->border.top <= wl_fixed_to_int(y) && + wl_fixed_to_int(y) < height + c->border.top; + + if (!input->focus && inside) { + notify_pointer_focus(&input->base, &input->output->base, + x - wl_fixed_from_int(c->border.left), + y = wl_fixed_from_int(c->border.top)); + wl_pointer_set_cursor(input->pointer, + input->enter_serial, NULL, 0, 0); + } else if (input->focus && !inside) { + notify_pointer_focus(&input->base, NULL, 0, 0); + /* FIXME: Should set default cursor here. */ + } + + input->focus = inside; +} + +/* parent input interface */ +static void +input_handle_pointer_enter(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t x, wl_fixed_t y) +{ + struct wayland_input *input = data; + + /* XXX: If we get a modifier event immediately before the focus, + * we should try to keep the same serial. */ + input->enter_serial = serial; + input->output = wl_surface_get_user_data(surface); + check_focus(input, x, y); +} + +static void +input_handle_pointer_leave(void *data, struct wl_pointer *pointer, + uint32_t serial, struct wl_surface *surface) +{ + struct wayland_input *input = data; + + notify_pointer_focus(&input->base, NULL, 0, 0); + input->output = NULL; + input->focus = 0; +} + +static void +input_handle_motion(void *data, struct wl_pointer *pointer, + uint32_t time, wl_fixed_t x, wl_fixed_t y) +{ + struct wayland_input *input = data; + struct wayland_compositor *c = input->compositor; + + check_focus(input, x, y); + if (input->focus) + notify_motion(&input->base, time, + x - wl_fixed_from_int(c->border.left) - + input->base.pointer->x, + y - wl_fixed_from_int(c->border.top) - + input->base.pointer->y); +} + +static void +input_handle_button(void *data, struct wl_pointer *pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t state_w) +{ + struct wayland_input *input = data; + enum wl_pointer_button_state state = state_w; + + notify_button(&input->base, time, button, state); +} + +static void +input_handle_axis(void *data, struct wl_pointer *pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ + struct wayland_input *input = data; + + notify_axis(&input->base, time, axis, value); +} + +static const struct wl_pointer_listener pointer_listener = { + input_handle_pointer_enter, + input_handle_pointer_leave, + input_handle_motion, + input_handle_button, + input_handle_axis, +}; + +static void +input_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format, + int fd, uint32_t size) +{ + struct wayland_input *input = data; + struct xkb_keymap *keymap; + char *map_str; + + if (!data) { + close(fd); + return; + } + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + + map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (map_str == MAP_FAILED) { + close(fd); + return; + } + + keymap = xkb_map_new_from_string(input->compositor->base.xkb_context, + map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + 0); + munmap(map_str, size); + close(fd); + + if (!keymap) { + weston_log("failed to compile keymap\n"); + return; + } + + weston_seat_init_keyboard(&input->base, keymap); + xkb_map_unref(keymap); +} + +static void +input_handle_keyboard_enter(void *data, + struct wl_keyboard *keyboard, + uint32_t serial, + struct wl_surface *surface, + struct wl_array *keys) +{ + struct wayland_input *input = data; + + /* XXX: If we get a modifier event immediately before the focus, + * we should try to keep the same serial. */ + notify_keyboard_focus_in(&input->base, keys, + STATE_UPDATE_AUTOMATIC); +} + +static void +input_handle_keyboard_leave(void *data, + struct wl_keyboard *keyboard, + uint32_t serial, + struct wl_surface *surface) +{ + struct wayland_input *input = data; + + notify_keyboard_focus_out(&input->base); +} + +static void +input_handle_key(void *data, struct wl_keyboard *keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + struct wayland_input *input = data; + + input->key_serial = serial; + notify_key(&input->base, time, key, + state ? WL_KEYBOARD_KEY_STATE_PRESSED : + WL_KEYBOARD_KEY_STATE_RELEASED, + STATE_UPDATE_NONE); +} + +static void +input_handle_modifiers(void *data, struct wl_keyboard *keyboard, + uint32_t serial_in, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ + struct wayland_input *input = data; + struct wayland_compositor *c = input->compositor; + uint32_t serial_out; + + /* If we get a key event followed by a modifier event with the + * same serial number, then we try to preserve those semantics by + * reusing the same serial number on the way out too. */ + if (serial_in == input->key_serial) + serial_out = wl_display_get_serial(c->base.wl_display); + else + serial_out = wl_display_next_serial(c->base.wl_display); + + xkb_state_update_mask(input->base.xkb_state.state, + mods_depressed, mods_latched, + mods_locked, 0, 0, group); + notify_modifiers(&input->base, serial_out); +} + +static const struct wl_keyboard_listener keyboard_listener = { + input_handle_keymap, + input_handle_keyboard_enter, + input_handle_keyboard_leave, + input_handle_key, + input_handle_modifiers, +}; + +static void +input_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + struct wayland_input *input = data; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !input->pointer) { + input->pointer = wl_seat_get_pointer(seat); + wl_pointer_set_user_data(input->pointer, input); + wl_pointer_add_listener(input->pointer, &pointer_listener, + input); + weston_seat_init_pointer(&input->base); + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->pointer) { + wl_pointer_destroy(input->pointer); + input->pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard) { + input->keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_set_user_data(input->keyboard, input); + wl_keyboard_add_listener(input->keyboard, &keyboard_listener, + input); + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard) { + wl_keyboard_destroy(input->keyboard); + input->keyboard = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + input_handle_capabilities, +}; + +static void +display_add_seat(struct wayland_compositor *c, uint32_t id) +{ + struct wayland_input *input; + + input = zalloc(sizeof *input); + if (input == NULL) + return; + + weston_seat_init(&input->base, &c->base, "default"); + input->compositor = c; + input->seat = wl_registry_bind(c->parent.registry, id, + &wl_seat_interface, 1); + wl_list_insert(c->input_list.prev, &input->link); + + wl_seat_add_listener(input->seat, &seat_listener, input); + wl_seat_set_user_data(input->seat, input); +} + +static void +registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, + const char *interface, uint32_t version) +{ + struct wayland_compositor *c = data; + + if (strcmp(interface, "wl_compositor") == 0) { + c->parent.compositor = + wl_registry_bind(registry, name, + &wl_compositor_interface, 1); + } else if (strcmp(interface, "wl_output") == 0) { + c->parent.output = + wl_registry_bind(registry, name, + &wl_output_interface, 1); + wl_output_add_listener(c->parent.output, &output_listener, c); + } else if (strcmp(interface, "wl_shell") == 0) { + c->parent.shell = + wl_registry_bind(registry, name, + &wl_shell_interface, 1); + } else if (strcmp(interface, "wl_seat") == 0) { + display_add_seat(c, name); + } else if (strcmp(interface, "wl_shm") == 0) { + c->parent.shm = + wl_registry_bind(registry, name, &wl_shm_interface, 1); + } +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global +}; + +static int +wayland_compositor_handle_event(int fd, uint32_t mask, void *data) +{ + struct wayland_compositor *c = data; + int count = 0; + + if (mask & WL_EVENT_READABLE) + count = wl_display_dispatch(c->parent.wl_display); + if (mask & WL_EVENT_WRITABLE) + wl_display_flush(c->parent.wl_display); + + if (mask == 0) { + count = wl_display_dispatch_pending(c->parent.wl_display); + wl_display_flush(c->parent.wl_display); + } + + return count; +} + +static void +wayland_restore(struct weston_compositor *ec) +{ +} + +static void +wayland_destroy(struct weston_compositor *ec) +{ + struct wayland_compositor *c = (struct wayland_compositor *) ec; + + ec->renderer->destroy(ec); + + weston_compositor_shutdown(ec); + + if (c->parent.shm) + wl_shm_destroy(c->parent.shm); + + free(ec); +} + +static struct weston_compositor * +wayland_compositor_create(struct wl_display *display, + int width, int height, const char *display_name, + int *argc, char *argv[], + struct weston_config *config) +{ + struct wayland_compositor *c; + struct wl_event_loop *loop; + int fd; + + c = zalloc(sizeof *c); + if (c == NULL) + return NULL; + + if (weston_compositor_init(&c->base, display, argc, argv, + config) < 0) + goto err_free; + + c->parent.wl_display = wl_display_connect(display_name); + + if (c->parent.wl_display == NULL) { + weston_log("failed to create display: %m\n"); + goto err_compositor; + } + + wl_list_init(&c->input_list); + c->parent.registry = wl_display_get_registry(c->parent.wl_display); + wl_registry_add_listener(c->parent.registry, ®istry_listener, c); + wl_display_dispatch(c->parent.wl_display); + + c->base.wl_display = display; + if (gl_renderer_create(&c->base, c->parent.wl_display, + gl_renderer_alpha_attribs, + NULL) < 0) + goto err_display; + + c->base.destroy = wayland_destroy; + c->base.restore = wayland_restore; + + c->border.top = 30; + c->border.bottom = 24; + c->border.left = 25; + c->border.right = 26; + + /* requires border fields */ + if (wayland_compositor_create_output(c, width, height) < 0) + goto err_gl; + + /* requires gl_renderer_output_state_create called + * by wayland_compositor_create_output */ + create_border(c); + + loop = wl_display_get_event_loop(c->base.wl_display); + + fd = wl_display_get_fd(c->parent.wl_display); + c->parent.wl_source = + wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + wayland_compositor_handle_event, c); + if (c->parent.wl_source == NULL) + goto err_gl; + + wl_event_source_check(c->parent.wl_source); + + return &c->base; + +err_gl: + c->base.renderer->destroy(&c->base); +err_display: + wl_display_disconnect(c->parent.wl_display); +err_compositor: + weston_compositor_shutdown(&c->base); +err_free: + free(c); + return NULL; +} + +WL_EXPORT struct weston_compositor * +backend_init(struct wl_display *display, int *argc, char *argv[], + struct weston_config *config) +{ + int width = 1024, height = 640; + char *display_name = NULL; + + const struct weston_option wayland_options[] = { + { WESTON_OPTION_INTEGER, "width", 0, &width }, + { WESTON_OPTION_INTEGER, "height", 0, &height }, + { WESTON_OPTION_STRING, "display", 0, &display_name }, + }; + + parse_options(wayland_options, + ARRAY_LENGTH(wayland_options), argc, argv); + + return wayland_compositor_create(display, width, height, display_name, + argc, argv, config); +} diff --git a/src/compositor-x11.c b/src/compositor-x11.c new file mode 100644 index 00000000..e6022f77 --- /dev/null +++ b/src/compositor-x11.c @@ -0,0 +1,1621 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2010-2011 Intel Corporation + * Copyright © 2013 Vasily Khoruzhick + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef HAVE_XCB_XKB +#include +#endif + +#include +#include + +#include + +#include "compositor.h" +#include "gl-renderer.h" +#include "pixman-renderer.h" +#include "../shared/config-parser.h" +#include "../shared/image-loader.h" + +#define DEFAULT_AXIS_STEP_DISTANCE wl_fixed_from_int(10) + +static int option_width; +static int option_height; +static int option_count; + +struct x11_compositor { + struct weston_compositor base; + + Display *dpy; + xcb_connection_t *conn; + xcb_screen_t *screen; + xcb_cursor_t null_cursor; + struct wl_array keys; + struct wl_event_source *xcb_source; + struct xkb_keymap *xkb_keymap; + unsigned int has_xkb; + uint8_t xkb_event_base; + int use_pixman; + + int has_net_wm_state_fullscreen; + + /* We could map multi-pointer X to multiple wayland seats, but + * for now we only support core X input. */ + struct weston_seat core_seat; + wl_fixed_t prev_x; + wl_fixed_t prev_y; + + struct { + xcb_atom_t wm_protocols; + xcb_atom_t wm_normal_hints; + xcb_atom_t wm_size_hints; + xcb_atom_t wm_delete_window; + xcb_atom_t wm_class; + xcb_atom_t net_wm_name; + xcb_atom_t net_supporting_wm_check; + xcb_atom_t net_supported; + xcb_atom_t net_wm_icon; + xcb_atom_t net_wm_state; + xcb_atom_t net_wm_state_fullscreen; + xcb_atom_t string; + xcb_atom_t utf8_string; + xcb_atom_t cardinal; + xcb_atom_t xkb_names; + } atom; +}; + +struct x11_output { + struct weston_output base; + + xcb_window_t window; + struct weston_mode mode; + struct wl_event_source *finish_frame_timer; + + xcb_gc_t gc; + xcb_shm_seg_t segment; + pixman_image_t *hw_surface; + int shm_id; + void *buf; + uint8_t depth; + int32_t scale; +}; + +static struct xkb_keymap * +x11_compositor_get_keymap(struct x11_compositor *c) +{ + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *reply; + struct xkb_rule_names names; + struct xkb_keymap *ret; + const char *value_all, *value_part; + int length_all, length_part; + + memset(&names, 0, sizeof(names)); + + cookie = xcb_get_property(c->conn, 0, c->screen->root, + c->atom.xkb_names, c->atom.string, 0, 1024); + reply = xcb_get_property_reply(c->conn, cookie, NULL); + if (reply == NULL) + return NULL; + + value_all = xcb_get_property_value(reply); + length_all = xcb_get_property_value_length(reply); + value_part = value_all; + +#define copy_prop_value(to) \ + length_part = strlen(value_part); \ + if (value_part + length_part < (value_all + length_all) && \ + length_part > 0) \ + names.to = value_part; \ + value_part += length_part + 1; + + copy_prop_value(rules); + copy_prop_value(model); + copy_prop_value(layout); + copy_prop_value(variant); + copy_prop_value(options); +#undef copy_prop_value + + ret = xkb_map_new_from_names(c->base.xkb_context, &names, 0); + + free(reply); + return ret; +} + +static uint32_t +get_xkb_mod_mask(struct x11_compositor *c, uint32_t in) +{ + struct weston_xkb_info *info = c->core_seat.xkb_info; + uint32_t ret = 0; + + if ((in & ShiftMask) && info->shift_mod != XKB_MOD_INVALID) + ret |= (1 << info->shift_mod); + if ((in & LockMask) && info->caps_mod != XKB_MOD_INVALID) + ret |= (1 << info->caps_mod); + if ((in & ControlMask) && info->ctrl_mod != XKB_MOD_INVALID) + ret |= (1 << info->ctrl_mod); + if ((in & Mod1Mask) && info->alt_mod != XKB_MOD_INVALID) + ret |= (1 << info->alt_mod); + if ((in & Mod2Mask) && info->mod2_mod != XKB_MOD_INVALID) + ret |= (1 << info->mod2_mod); + if ((in & Mod3Mask) && info->mod3_mod != XKB_MOD_INVALID) + ret |= (1 << info->mod3_mod); + if ((in & Mod4Mask) && info->super_mod != XKB_MOD_INVALID) + ret |= (1 << info->super_mod); + if ((in & Mod5Mask) && info->mod5_mod != XKB_MOD_INVALID) + ret |= (1 << info->mod5_mod); + + return ret; +} + +static void +x11_compositor_setup_xkb(struct x11_compositor *c) +{ +#ifndef HAVE_XCB_XKB + weston_log("XCB-XKB not available during build\n"); + c->has_xkb = 0; + c->xkb_event_base = 0; + return; +#else + const xcb_query_extension_reply_t *ext; + xcb_generic_error_t *error; + xcb_void_cookie_t select; + xcb_xkb_use_extension_cookie_t use_ext; + xcb_xkb_use_extension_reply_t *use_ext_reply; + xcb_xkb_per_client_flags_cookie_t pcf; + xcb_xkb_per_client_flags_reply_t *pcf_reply; + xcb_xkb_get_state_cookie_t state; + xcb_xkb_get_state_reply_t *state_reply; + + c->has_xkb = 0; + c->xkb_event_base = 0; + + ext = xcb_get_extension_data(c->conn, &xcb_xkb_id); + if (!ext) { + weston_log("XKB extension not available on host X11 server\n"); + return; + } + c->xkb_event_base = ext->first_event; + + select = xcb_xkb_select_events_checked(c->conn, + XCB_XKB_ID_USE_CORE_KBD, + XCB_XKB_EVENT_TYPE_STATE_NOTIFY, + 0, + XCB_XKB_EVENT_TYPE_STATE_NOTIFY, + 0, + 0, + NULL); + error = xcb_request_check(c->conn, select); + if (error) { + weston_log("error: failed to select for XKB state events\n"); + free(error); + return; + } + + use_ext = xcb_xkb_use_extension(c->conn, + XCB_XKB_MAJOR_VERSION, + XCB_XKB_MINOR_VERSION); + use_ext_reply = xcb_xkb_use_extension_reply(c->conn, use_ext, NULL); + if (!use_ext_reply) { + weston_log("couldn't start using XKB extension\n"); + return; + } + + if (!use_ext_reply->supported) { + weston_log("XKB extension version on the server is too old " + "(want %d.%d, has %d.%d)\n", + XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION, + use_ext_reply->serverMajor, use_ext_reply->serverMinor); + free(use_ext_reply); + return; + } + free(use_ext_reply); + + pcf = xcb_xkb_per_client_flags(c->conn, + XCB_XKB_ID_USE_CORE_KBD, + XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, + XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, + 0, + 0, + 0); + pcf_reply = xcb_xkb_per_client_flags_reply(c->conn, pcf, NULL); + if (!pcf_reply || + !(pcf_reply->value & XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT)) { + weston_log("failed to set XKB per-client flags, not using " + "detectable repeat\n"); + free(pcf_reply); + return; + } + free(pcf_reply); + + state = xcb_xkb_get_state(c->conn, XCB_XKB_ID_USE_CORE_KBD); + state_reply = xcb_xkb_get_state_reply(c->conn, state, NULL); + if (!state_reply) { + weston_log("failed to get initial XKB state\n"); + return; + } + + xkb_state_update_mask(c->core_seat.xkb_state.state, + get_xkb_mod_mask(c, state_reply->baseMods), + get_xkb_mod_mask(c, state_reply->latchedMods), + get_xkb_mod_mask(c, state_reply->lockedMods), + 0, + 0, + state_reply->group); + + free(state_reply); + + c->has_xkb = 1; +#endif +} + +static int +x11_input_create(struct x11_compositor *c, int no_input) +{ + struct xkb_keymap *keymap; + + weston_seat_init(&c->core_seat, &c->base, "default"); + + if (no_input) + return 0; + + weston_seat_init_pointer(&c->core_seat); + + keymap = x11_compositor_get_keymap(c); + if (weston_seat_init_keyboard(&c->core_seat, keymap) < 0) + return -1; + if (keymap) + xkb_map_unref(keymap); + + x11_compositor_setup_xkb(c); + + return 0; +} + +static void +x11_input_destroy(struct x11_compositor *compositor) +{ + weston_seat_release(&compositor->core_seat); +} + +static void +x11_output_start_repaint_loop(struct weston_output *output) +{ + uint32_t msec; + struct timeval tv; + + gettimeofday(&tv, NULL); + msec = tv.tv_sec * 1000 + tv.tv_usec / 1000; + weston_output_finish_frame(output, msec); +} + +static int +x11_output_repaint_gl(struct weston_output *output_base, + pixman_region32_t *damage) +{ + struct x11_output *output = (struct x11_output *)output_base; + struct weston_compositor *ec = output->base.compositor; + + ec->renderer->repaint_output(output_base, damage); + + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + + wl_event_source_timer_update(output->finish_frame_timer, 10); + return 0; +} + +static void +set_clip_for_output(struct weston_output *output_base, pixman_region32_t *region) +{ + struct x11_output *output = (struct x11_output *)output_base; + struct weston_compositor *ec = output->base.compositor; + struct x11_compositor *c = (struct x11_compositor *)ec; + pixman_box32_t *rects; + xcb_rectangle_t *output_rects; + pixman_box32_t rect, transformed_rect; + xcb_void_cookie_t cookie; + int width, height, nrects, i; + xcb_generic_error_t *err; + + rects = pixman_region32_rectangles(region, &nrects); + output_rects = calloc(nrects, sizeof(xcb_rectangle_t)); + + if (output_rects == NULL) + return; + + width = output_base->width; + height = output_base->height; + + for (i = 0; i < nrects; i++) { + rect = rects[i]; + rect.x1 -= output_base->x; + rect.y1 -= output_base->y; + rect.x2 -= output_base->x; + rect.y2 -= output_base->y; + + switch (output_base->transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + transformed_rect.x1 = rect.x1; + transformed_rect.y1 = rect.y1; + transformed_rect.x2 = rect.x2; + transformed_rect.y2 = rect.y2; + break; + case WL_OUTPUT_TRANSFORM_90: + transformed_rect.x1 = height - rect.y2; + transformed_rect.y1 = rect.x1; + transformed_rect.x2 = height - rect.y1; + transformed_rect.y2 = rect.x2; + break; + case WL_OUTPUT_TRANSFORM_180: + transformed_rect.x1 = width - rect.x2; + transformed_rect.y1 = height - rect.y2; + transformed_rect.x2 = width - rect.x1; + transformed_rect.y2 = height - rect.y1; + break; + case WL_OUTPUT_TRANSFORM_270: + transformed_rect.x1 = rect.y1; + transformed_rect.y1 = width - rect.x2; + transformed_rect.x2 = rect.y2; + transformed_rect.y2 = width - rect.x1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + transformed_rect.x1 = width - rect.x2; + transformed_rect.y1 = rect.y1; + transformed_rect.x2 = width - rect.x1; + transformed_rect.y2 = rect.y2; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + transformed_rect.x1 = height - rect.y2; + transformed_rect.y1 = width - rect.x2; + transformed_rect.x2 = height - rect.y1; + transformed_rect.y2 = width - rect.x1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + transformed_rect.x1 = rect.x1; + transformed_rect.y1 = height - rect.y2; + transformed_rect.x2 = rect.x2; + transformed_rect.y2 = height - rect.y1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + transformed_rect.x1 = rect.y1; + transformed_rect.y1 = rect.x1; + transformed_rect.x2 = rect.y2; + transformed_rect.y2 = rect.x2; + break; + } + + transformed_rect.x1 *= output_base->current_scale; + transformed_rect.y1 *= output_base->current_scale; + transformed_rect.x2 *= output_base->current_scale; + transformed_rect.y2 *= output_base->current_scale; + + output_rects[i].x = transformed_rect.x1; + output_rects[i].y = transformed_rect.y1; + output_rects[i].width = transformed_rect.x2 - transformed_rect.x1; + output_rects[i].height = transformed_rect.y2 - transformed_rect.y1; + } + + cookie = xcb_set_clip_rectangles_checked(c->conn, XCB_CLIP_ORDERING_UNSORTED, + output->gc, + 0, 0, nrects, + output_rects); + err = xcb_request_check(c->conn, cookie); + if (err != NULL) { + weston_log("Failed to set clip rects, err: %d\n", err->error_code); + free(err); + } + free(output_rects); +} + + +static int +x11_output_repaint_shm(struct weston_output *output_base, + pixman_region32_t *damage) +{ + struct x11_output *output = (struct x11_output *)output_base; + struct weston_compositor *ec = output->base.compositor; + struct x11_compositor *c = (struct x11_compositor *)ec; + xcb_void_cookie_t cookie; + xcb_generic_error_t *err; + + pixman_renderer_output_set_buffer(output_base, output->hw_surface); + ec->renderer->repaint_output(output_base, damage); + + pixman_region32_subtract(&ec->primary_plane.damage, + &ec->primary_plane.damage, damage); + set_clip_for_output(output_base, damage); + cookie = xcb_shm_put_image_checked(c->conn, output->window, output->gc, + pixman_image_get_width(output->hw_surface), + pixman_image_get_height(output->hw_surface), + 0, 0, + pixman_image_get_width(output->hw_surface), + pixman_image_get_height(output->hw_surface), + 0, 0, output->depth, XCB_IMAGE_FORMAT_Z_PIXMAP, + 0, output->segment, 0); + err = xcb_request_check(c->conn, cookie); + if (err != NULL) { + weston_log("Failed to put shm image, err: %d\n", err->error_code); + free(err); + } + + wl_event_source_timer_update(output->finish_frame_timer, 10); + return 0; +} + +static int +finish_frame_handler(void *data) +{ + struct x11_output *output = data; + + x11_output_start_repaint_loop(&output->base); + + return 1; +} + +static void +x11_output_deinit_shm(struct x11_compositor *c, struct x11_output *output) +{ + xcb_void_cookie_t cookie; + xcb_generic_error_t *err; + xcb_free_gc(c->conn, output->gc); + + pixman_image_unref(output->hw_surface); + output->hw_surface = NULL; + cookie = xcb_shm_detach_checked(c->conn, output->segment); + err = xcb_request_check(c->conn, cookie); + if (err) { + weston_log("xcb_shm_detach failed, error %d\n", err->error_code); + free(err); + } + shmdt(output->buf); +} + +static void +x11_output_destroy(struct weston_output *output_base) +{ + struct x11_output *output = (struct x11_output *)output_base; + struct x11_compositor *compositor = + (struct x11_compositor *)output->base.compositor; + + wl_list_remove(&output->base.link); + wl_event_source_remove(output->finish_frame_timer); + + if (compositor->use_pixman) { + pixman_renderer_output_destroy(output_base); + x11_output_deinit_shm(compositor, output); + } else + gl_renderer_output_destroy(output_base); + + xcb_destroy_window(compositor->conn, output->window); + + weston_output_destroy(&output->base); + + free(output); +} + +static void +x11_output_set_wm_protocols(struct x11_compositor *c, + struct x11_output *output) +{ + xcb_atom_t list[1]; + + list[0] = c->atom.wm_delete_window; + xcb_change_property (c->conn, + XCB_PROP_MODE_REPLACE, + output->window, + c->atom.wm_protocols, + XCB_ATOM_ATOM, + 32, + ARRAY_LENGTH(list), + list); +} + +struct wm_normal_hints { + uint32_t flags; + uint32_t pad[4]; + int32_t min_width, min_height; + int32_t max_width, max_height; + int32_t width_inc, height_inc; + int32_t min_aspect_x, min_aspect_y; + int32_t max_aspect_x, max_aspect_y; + int32_t base_width, base_height; + int32_t win_gravity; +}; + +#define WM_NORMAL_HINTS_MIN_SIZE 16 +#define WM_NORMAL_HINTS_MAX_SIZE 32 + +static void +x11_output_set_icon(struct x11_compositor *c, + struct x11_output *output, const char *filename) +{ + uint32_t *icon; + int32_t width, height; + pixman_image_t *image; + + image = load_image(filename); + if (!image) + return; + width = pixman_image_get_width(image); + height = pixman_image_get_height(image); + icon = malloc(width * height * 4 + 8); + if (!icon) { + pixman_image_unref(image); + return; + } + + icon[0] = width; + icon[1] = height; + memcpy(icon + 2, pixman_image_get_data(image), width * height * 4); + xcb_change_property(c->conn, XCB_PROP_MODE_REPLACE, output->window, + c->atom.net_wm_icon, c->atom.cardinal, 32, + width * height + 2, icon); + free(icon); + pixman_image_unref(image); +} + +static void +x11_output_wait_for_map(struct x11_compositor *c, struct x11_output *output) +{ + xcb_map_notify_event_t *map_notify; + xcb_configure_notify_event_t *configure_notify; + xcb_generic_event_t *event; + int mapped = 0, configured = 0; + uint8_t response_type; + + /* This isn't the nicest way to do this. Ideally, we could + * just go back to the main loop and once we get the configure + * notify, we add the output to the compositor. While we do + * support output hotplug, we can't start up with no outputs. + * We could add the output and then resize once we get the + * configure notify, but we don't want to start up and + * immediately resize the output. + * + * Also, some window managers don't give us our final + * fullscreen size before map_notify, so if we don't get a + * configure_notify before map_notify, we just wait for the + * first one and hope that's our size. */ + + xcb_flush(c->conn); + + while (!mapped || !configured) { + event = xcb_wait_for_event(c->conn); + response_type = event->response_type & ~0x80; + + switch (response_type) { + case XCB_MAP_NOTIFY: + map_notify = (xcb_map_notify_event_t *) event; + if (map_notify->window == output->window) + mapped = 1; + break; + + case XCB_CONFIGURE_NOTIFY: + configure_notify = + (xcb_configure_notify_event_t *) event; + + + if (configure_notify->width % output->scale != 0 || + configure_notify->height % output->scale != 0) + weston_log("Resolution is not a multiple of screen size, rounding\n"); + output->mode.width = configure_notify->width / output->scale; + output->mode.height = configure_notify->height / output->scale; + configured = 1; + break; + } + } +} + +static xcb_visualtype_t * +find_visual_by_id(xcb_screen_t *screen, + xcb_visualid_t id) +{ + xcb_depth_iterator_t i; + xcb_visualtype_iterator_t j; + for (i = xcb_screen_allowed_depths_iterator(screen); + i.rem; + xcb_depth_next(&i)) { + for (j = xcb_depth_visuals_iterator(i.data); + j.rem; + xcb_visualtype_next(&j)) { + if (j.data->visual_id == id) + return j.data; + } + } + return 0; +} + +static uint8_t +get_depth_of_visual(xcb_screen_t *screen, + xcb_visualid_t id) +{ + xcb_depth_iterator_t i; + xcb_visualtype_iterator_t j; + for (i = xcb_screen_allowed_depths_iterator(screen); + i.rem; + xcb_depth_next(&i)) { + for (j = xcb_depth_visuals_iterator(i.data); + j.rem; + xcb_visualtype_next(&j)) { + if (j.data->visual_id == id) + return i.data->depth; + } + } + return 0; +} + +static int +x11_output_init_shm(struct x11_compositor *c, struct x11_output *output, + int width, int height) +{ + xcb_screen_iterator_t iter; + xcb_visualtype_t *visual_type; + xcb_format_iterator_t fmt; + xcb_void_cookie_t cookie; + xcb_generic_error_t *err; + const xcb_query_extension_reply_t *ext; + int bitsperpixel = 0; + pixman_format_code_t pixman_format; + + /* Check if SHM is available */ + ext = xcb_get_extension_data(c->conn, &xcb_shm_id); + if (ext == NULL || !ext->present) { + /* SHM is missing */ + weston_log("SHM extension is not available\n"); + errno = ENOENT; + return -1; + } + + iter = xcb_setup_roots_iterator(xcb_get_setup(c->conn)); + visual_type = find_visual_by_id(iter.data, iter.data->root_visual); + if (!visual_type) { + weston_log("Failed to lookup visual for root window\n"); + errno = ENOENT; + return -1; + } + weston_log("Found visual, bits per value: %d, red_mask: %.8x, green_mask: %.8x, blue_mask: %.8x\n", + visual_type->bits_per_rgb_value, + visual_type->red_mask, + visual_type->green_mask, + visual_type->blue_mask); + output->depth = get_depth_of_visual(iter.data, iter.data->root_visual); + weston_log("Visual depth is %d\n", output->depth); + + for (fmt = xcb_setup_pixmap_formats_iterator(xcb_get_setup(c->conn)); + fmt.rem; + xcb_format_next(&fmt)) { + if (fmt.data->depth == output->depth) { + bitsperpixel = fmt.data->bits_per_pixel; + break; + } + } + weston_log("Found format for depth %d, bpp: %d\n", + output->depth, bitsperpixel); + + if (bitsperpixel == 32 && + visual_type->red_mask == 0xff0000 && + visual_type->green_mask == 0x00ff00 && + visual_type->blue_mask == 0x0000ff) { + weston_log("Will use x8r8g8b8 format for SHM surfaces\n"); + pixman_format = PIXMAN_x8r8g8b8; + } else { + weston_log("Can't find appropriate format for SHM pixmap\n"); + errno = ENOTSUP; + return -1; + } + + + /* Create SHM segment and attach it */ + output->shm_id = shmget(IPC_PRIVATE, width * height * (bitsperpixel / 8), IPC_CREAT | S_IRWXU); + if (output->shm_id == -1) { + weston_log("x11shm: failed to allocate SHM segment\n"); + return -1; + } + output->buf = shmat(output->shm_id, NULL, 0 /* read/write */); + if (-1 == (long)output->buf) { + weston_log("x11shm: failed to attach SHM segment\n"); + return -1; + } + output->segment = xcb_generate_id(c->conn); + cookie = xcb_shm_attach_checked(c->conn, output->segment, output->shm_id, 1); + err = xcb_request_check(c->conn, cookie); + if (err) { + weston_log("x11shm: xcb_shm_attach error %d\n", err->error_code); + free(err); + return -1; + } + + shmctl(output->shm_id, IPC_RMID, NULL); + + /* Now create pixman image */ + output->hw_surface = pixman_image_create_bits(pixman_format, width, height, output->buf, + width * (bitsperpixel / 8)); + + output->gc = xcb_generate_id(c->conn); + xcb_create_gc(c->conn, output->gc, output->window, 0, NULL); + + return 0; +} + +static struct x11_output * +x11_compositor_create_output(struct x11_compositor *c, int x, int y, + int width, int height, int fullscreen, + int no_input, char *configured_name, + uint32_t transform, int32_t scale) +{ + static const char name[] = "Weston Compositor"; + static const char class[] = "weston-1\0Weston Compositor"; + char title[32]; + struct x11_output *output; + xcb_screen_iterator_t iter; + struct wm_normal_hints normal_hints; + struct wl_event_loop *loop; + int output_width, output_height; + uint32_t mask = XCB_CW_EVENT_MASK | XCB_CW_CURSOR; + xcb_atom_t atom_list[1]; + uint32_t values[2] = { + XCB_EVENT_MASK_EXPOSURE | + XCB_EVENT_MASK_STRUCTURE_NOTIFY, + 0 + }; + + output_width = width * scale; + output_height = height * scale; + + if (configured_name) + sprintf(title, "%s - %s", name, configured_name); + else + strcpy(title, name); + + if (!no_input) + values[0] |= + XCB_EVENT_MASK_KEY_PRESS | + XCB_EVENT_MASK_KEY_RELEASE | + XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_BUTTON_RELEASE | + XCB_EVENT_MASK_POINTER_MOTION | + XCB_EVENT_MASK_ENTER_WINDOW | + XCB_EVENT_MASK_LEAVE_WINDOW | + XCB_EVENT_MASK_KEYMAP_STATE | + XCB_EVENT_MASK_FOCUS_CHANGE; + + output = zalloc(sizeof *output); + if (output == NULL) + return NULL; + + output->mode.flags = + WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; + + output->mode.width = output_width; + output->mode.height = output_height; + output->mode.refresh = 60000; + output->scale = scale; + wl_list_init(&output->base.mode_list); + wl_list_insert(&output->base.mode_list, &output->mode.link); + + values[1] = c->null_cursor; + output->window = xcb_generate_id(c->conn); + iter = xcb_setup_roots_iterator(xcb_get_setup(c->conn)); + xcb_create_window(c->conn, + XCB_COPY_FROM_PARENT, + output->window, + iter.data->root, + 0, 0, + output_width, output_height, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + iter.data->root_visual, + mask, values); + + if (fullscreen) { + atom_list[0] = c->atom.net_wm_state_fullscreen; + xcb_change_property(c->conn, XCB_PROP_MODE_REPLACE, + output->window, + c->atom.net_wm_state, + XCB_ATOM_ATOM, 32, + ARRAY_LENGTH(atom_list), atom_list); + } else { + /* Don't resize me. */ + memset(&normal_hints, 0, sizeof normal_hints); + normal_hints.flags = + WM_NORMAL_HINTS_MAX_SIZE | WM_NORMAL_HINTS_MIN_SIZE; + normal_hints.min_width = output_width; + normal_hints.min_height = output_height; + normal_hints.max_width = output_width; + normal_hints.max_height = output_height; + xcb_change_property(c->conn, XCB_PROP_MODE_REPLACE, output->window, + c->atom.wm_normal_hints, + c->atom.wm_size_hints, 32, + sizeof normal_hints / 4, + (uint8_t *) &normal_hints); + } + + /* Set window name. Don't bother with non-EWMH WMs. */ + xcb_change_property(c->conn, XCB_PROP_MODE_REPLACE, output->window, + c->atom.net_wm_name, c->atom.utf8_string, 8, + strlen(title), title); + xcb_change_property(c->conn, XCB_PROP_MODE_REPLACE, output->window, + c->atom.wm_class, c->atom.string, 8, + sizeof class, class); + + x11_output_set_icon(c, output, DATADIR "/weston/wayland.png"); + + x11_output_set_wm_protocols(c, output); + + xcb_map_window(c->conn, output->window); + + if (fullscreen) + x11_output_wait_for_map(c, output); + + output->base.start_repaint_loop = x11_output_start_repaint_loop; + if (c->use_pixman) + output->base.repaint = x11_output_repaint_shm; + else + output->base.repaint = x11_output_repaint_gl; + output->base.destroy = x11_output_destroy; + output->base.assign_planes = NULL; + output->base.set_backlight = NULL; + output->base.set_dpms = NULL; + output->base.switch_mode = NULL; + output->base.current_mode = &output->mode; + output->base.make = "xwayland"; + output->base.model = "none"; + weston_output_init(&output->base, &c->base, + x, y, width, height, transform, scale); + + if (c->use_pixman) { + if (x11_output_init_shm(c, output, + output->mode.width, + output->mode.height) < 0) + return NULL; + if (pixman_renderer_output_create(&output->base) < 0) { + x11_output_deinit_shm(c, output); + return NULL; + } + } else { + if (gl_renderer_output_create(&output->base, (EGLNativeWindowType)output->window) < 0) + return NULL; + } + + loop = wl_display_get_event_loop(c->base.wl_display); + output->finish_frame_timer = + wl_event_loop_add_timer(loop, finish_frame_handler, output); + + wl_list_insert(c->base.output_list.prev, &output->base.link); + + weston_log("x11 output %dx%d, window id %d\n", + width, height, output->window); + + return output; +} + +static struct x11_output * +x11_compositor_find_output(struct x11_compositor *c, xcb_window_t window) +{ + struct x11_output *output; + + wl_list_for_each(output, &c->base.output_list, base.link) { + if (output->window == window) + return output; + } + + assert(0); +} + +#ifdef HAVE_XCB_XKB +static void +update_xkb_state(struct x11_compositor *c, xcb_xkb_state_notify_event_t *state) +{ + xkb_state_update_mask(c->core_seat.xkb_state.state, + get_xkb_mod_mask(c, state->baseMods), + get_xkb_mod_mask(c, state->latchedMods), + get_xkb_mod_mask(c, state->lockedMods), + 0, + 0, + state->group); + + notify_modifiers(&c->core_seat, + wl_display_next_serial(c->base.wl_display)); +} +#endif + +/** + * This is monumentally unpleasant. If we don't have XCB-XKB bindings, + * the best we can do (given that XCB also lacks XI2 support), is to take + * the state from the core key events. Unfortunately that only gives us + * the effective (i.e. union of depressed/latched/locked) state, and we + * need the granularity. + * + * So we still update the state with every key event we see, but also use + * the state field from X11 events as a mask so we don't get any stuck + * modifiers. + */ +static void +update_xkb_state_from_core(struct x11_compositor *c, uint16_t x11_mask) +{ + uint32_t mask = get_xkb_mod_mask(c, x11_mask); + struct weston_keyboard *keyboard = c->core_seat.keyboard; + + xkb_state_update_mask(c->core_seat.xkb_state.state, + keyboard->modifiers.mods_depressed & mask, + keyboard->modifiers.mods_latched & mask, + keyboard->modifiers.mods_locked & mask, + 0, + 0, + (x11_mask >> 13) & 3); + notify_modifiers(&c->core_seat, + wl_display_next_serial(c->base.wl_display)); +} + +static void +x11_compositor_deliver_button_event(struct x11_compositor *c, + xcb_generic_event_t *event, int state) +{ + xcb_button_press_event_t *button_event = + (xcb_button_press_event_t *) event; + uint32_t button; + struct x11_output *output; + + output = x11_compositor_find_output(c, button_event->event); + + if (state) + xcb_grab_pointer(c->conn, 0, output->window, + XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_BUTTON_RELEASE | + XCB_EVENT_MASK_POINTER_MOTION | + XCB_EVENT_MASK_ENTER_WINDOW | + XCB_EVENT_MASK_LEAVE_WINDOW, + XCB_GRAB_MODE_ASYNC, + XCB_GRAB_MODE_ASYNC, + output->window, XCB_CURSOR_NONE, + button_event->time); + else + xcb_ungrab_pointer(c->conn, button_event->time); + + if (!c->has_xkb) + update_xkb_state_from_core(c, button_event->state); + + switch (button_event->detail) { + default: + button = button_event->detail + BTN_LEFT - 1; + break; + case 2: + button = BTN_MIDDLE; + break; + case 3: + button = BTN_RIGHT; + break; + case 4: + /* Axis are measured in pixels, but the xcb events are discrete + * steps. Therefore move the axis by some pixels every step. */ + if (state) + notify_axis(&c->core_seat, + weston_compositor_get_time(), + WL_POINTER_AXIS_VERTICAL_SCROLL, + -DEFAULT_AXIS_STEP_DISTANCE); + return; + case 5: + if (state) + notify_axis(&c->core_seat, + weston_compositor_get_time(), + WL_POINTER_AXIS_VERTICAL_SCROLL, + DEFAULT_AXIS_STEP_DISTANCE); + return; + case 6: + if (state) + notify_axis(&c->core_seat, + weston_compositor_get_time(), + WL_POINTER_AXIS_HORIZONTAL_SCROLL, + -DEFAULT_AXIS_STEP_DISTANCE); + return; + case 7: + if (state) + notify_axis(&c->core_seat, + weston_compositor_get_time(), + WL_POINTER_AXIS_HORIZONTAL_SCROLL, + DEFAULT_AXIS_STEP_DISTANCE); + return; + } + + notify_button(&c->core_seat, + weston_compositor_get_time(), button, + state ? WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED); +} + +static void +x11_compositor_deliver_motion_event(struct x11_compositor *c, + xcb_generic_event_t *event) +{ + struct x11_output *output; + wl_fixed_t x, y; + xcb_motion_notify_event_t *motion_notify = + (xcb_motion_notify_event_t *) event; + + if (!c->has_xkb) + update_xkb_state_from_core(c, motion_notify->state); + output = x11_compositor_find_output(c, motion_notify->event); + weston_output_transform_coordinate(&output->base, + motion_notify->event_x, + motion_notify->event_y, &x, &y); + + notify_motion(&c->core_seat, weston_compositor_get_time(), + x - c->prev_x, y - c->prev_y); + + c->prev_x = x; + c->prev_y = y; +} + +static void +x11_compositor_deliver_enter_event(struct x11_compositor *c, + xcb_generic_event_t *event) +{ + struct x11_output *output; + wl_fixed_t x, y; + + xcb_enter_notify_event_t *enter_notify = + (xcb_enter_notify_event_t *) event; + if (enter_notify->state >= Button1Mask) + return; + if (!c->has_xkb) + update_xkb_state_from_core(c, enter_notify->state); + output = x11_compositor_find_output(c, enter_notify->event); + weston_output_transform_coordinate(&output->base, + enter_notify->event_x, + enter_notify->event_y, &x, &y); + + notify_pointer_focus(&c->core_seat, &output->base, x, y); + + c->prev_x = x; + c->prev_y = y; +} + +static int +x11_compositor_next_event(struct x11_compositor *c, + xcb_generic_event_t **event, uint32_t mask) +{ + if (mask & WL_EVENT_READABLE) { + *event = xcb_poll_for_event(c->conn); + } else { +#ifdef HAVE_XCB_POLL_FOR_QUEUED_EVENT + *event = xcb_poll_for_queued_event(c->conn); +#else + *event = xcb_poll_for_event(c->conn); +#endif + } + + return *event != NULL; +} + +static int +x11_compositor_handle_event(int fd, uint32_t mask, void *data) +{ + struct x11_compositor *c = data; + struct x11_output *output; + xcb_generic_event_t *event, *prev; + xcb_client_message_event_t *client_message; + xcb_enter_notify_event_t *enter_notify; + xcb_key_press_event_t *key_press, *key_release; + xcb_keymap_notify_event_t *keymap_notify; + xcb_focus_in_event_t *focus_in; + xcb_expose_event_t *expose; + xcb_atom_t atom; + uint32_t *k; + uint32_t i, set; + uint8_t response_type; + int count; + + prev = NULL; + count = 0; + while (x11_compositor_next_event(c, &event, mask)) { + response_type = event->response_type & ~0x80; + + switch (prev ? prev->response_type & ~0x80 : 0x80) { + case XCB_KEY_RELEASE: + /* Suppress key repeat events; this is only used if we + * don't have XCB XKB support. */ + key_release = (xcb_key_press_event_t *) prev; + key_press = (xcb_key_press_event_t *) event; + if (response_type == XCB_KEY_PRESS && + key_release->time == key_press->time && + key_release->detail == key_press->detail) { + /* Don't deliver the held key release + * event or the new key press event. */ + free(event); + free(prev); + prev = NULL; + continue; + } else { + /* Deliver the held key release now + * and fall through and handle the new + * event below. */ + update_xkb_state_from_core(c, key_release->state); + notify_key(&c->core_seat, + weston_compositor_get_time(), + key_release->detail - 8, + WL_KEYBOARD_KEY_STATE_RELEASED, + STATE_UPDATE_AUTOMATIC); + free(prev); + prev = NULL; + break; + } + + case XCB_FOCUS_IN: + assert(response_type == XCB_KEYMAP_NOTIFY); + keymap_notify = (xcb_keymap_notify_event_t *) event; + c->keys.size = 0; + for (i = 0; i < ARRAY_LENGTH(keymap_notify->keys) * 8; i++) { + set = keymap_notify->keys[i >> 3] & + (1 << (i & 7)); + if (set) { + k = wl_array_add(&c->keys, sizeof *k); + *k = i; + } + } + + /* Unfortunately the state only comes with the enter + * event, rather than with the focus event. I'm not + * sure of the exact semantics around it and whether + * we can ensure that we get both? */ + notify_keyboard_focus_in(&c->core_seat, &c->keys, + STATE_UPDATE_AUTOMATIC); + + free(prev); + prev = NULL; + break; + + default: + /* No previous event held */ + break; + } + + switch (response_type) { + case XCB_KEY_PRESS: + key_press = (xcb_key_press_event_t *) event; + if (!c->has_xkb) + update_xkb_state_from_core(c, key_press->state); + notify_key(&c->core_seat, + weston_compositor_get_time(), + key_press->detail - 8, + WL_KEYBOARD_KEY_STATE_PRESSED, + c->has_xkb ? STATE_UPDATE_NONE : + STATE_UPDATE_AUTOMATIC); + break; + case XCB_KEY_RELEASE: + /* If we don't have XKB, we need to use the lame + * autorepeat detection above. */ + if (!c->has_xkb) { + prev = event; + break; + } + key_release = (xcb_key_press_event_t *) event; + notify_key(&c->core_seat, + weston_compositor_get_time(), + key_release->detail - 8, + WL_KEYBOARD_KEY_STATE_RELEASED, + STATE_UPDATE_NONE); + break; + case XCB_BUTTON_PRESS: + x11_compositor_deliver_button_event(c, event, 1); + break; + case XCB_BUTTON_RELEASE: + x11_compositor_deliver_button_event(c, event, 0); + break; + case XCB_MOTION_NOTIFY: + x11_compositor_deliver_motion_event(c, event); + break; + + case XCB_EXPOSE: + expose = (xcb_expose_event_t *) event; + output = x11_compositor_find_output(c, expose->window); + weston_output_schedule_repaint(&output->base); + break; + + case XCB_ENTER_NOTIFY: + x11_compositor_deliver_enter_event(c, event); + break; + + case XCB_LEAVE_NOTIFY: + enter_notify = (xcb_enter_notify_event_t *) event; + if (enter_notify->state >= Button1Mask) + break; + if (!c->has_xkb) + update_xkb_state_from_core(c, enter_notify->state); + notify_pointer_focus(&c->core_seat, NULL, 0, 0); + break; + + case XCB_CLIENT_MESSAGE: + client_message = (xcb_client_message_event_t *) event; + atom = client_message->data.data32[0]; + if (atom == c->atom.wm_delete_window) + wl_display_terminate(c->base.wl_display); + break; + + case XCB_FOCUS_IN: + focus_in = (xcb_focus_in_event_t *) event; + if (focus_in->mode == XCB_NOTIFY_MODE_WHILE_GRABBED) + break; + + prev = event; + break; + + case XCB_FOCUS_OUT: + focus_in = (xcb_focus_in_event_t *) event; + if (focus_in->mode == XCB_NOTIFY_MODE_WHILE_GRABBED || + focus_in->mode == XCB_NOTIFY_MODE_UNGRAB) + break; + notify_keyboard_focus_out(&c->core_seat); + break; + + default: + break; + } + +#ifdef HAVE_XCB_XKB + if (c->has_xkb && + response_type == c->xkb_event_base) { + xcb_xkb_state_notify_event_t *state = + (xcb_xkb_state_notify_event_t *) event; + if (state->xkbType == XCB_XKB_STATE_NOTIFY) + update_xkb_state(c, state); + } +#endif + + count++; + if (prev != event) + free (event); + } + + switch (prev ? prev->response_type & ~0x80 : 0x80) { + case XCB_KEY_RELEASE: + key_release = (xcb_key_press_event_t *) prev; + update_xkb_state_from_core(c, key_release->state); + notify_key(&c->core_seat, + weston_compositor_get_time(), + key_release->detail - 8, + WL_KEYBOARD_KEY_STATE_RELEASED, + STATE_UPDATE_AUTOMATIC); + free(prev); + prev = NULL; + break; + default: + break; + } + + return count; +} + +#define F(field) offsetof(struct x11_compositor, field) + +static void +x11_compositor_get_resources(struct x11_compositor *c) +{ + static const struct { const char *name; int offset; } atoms[] = { + { "WM_PROTOCOLS", F(atom.wm_protocols) }, + { "WM_NORMAL_HINTS", F(atom.wm_normal_hints) }, + { "WM_SIZE_HINTS", F(atom.wm_size_hints) }, + { "WM_DELETE_WINDOW", F(atom.wm_delete_window) }, + { "WM_CLASS", F(atom.wm_class) }, + { "_NET_WM_NAME", F(atom.net_wm_name) }, + { "_NET_WM_ICON", F(atom.net_wm_icon) }, + { "_NET_WM_STATE", F(atom.net_wm_state) }, + { "_NET_WM_STATE_FULLSCREEN", F(atom.net_wm_state_fullscreen) }, + { "_NET_SUPPORTING_WM_CHECK", + F(atom.net_supporting_wm_check) }, + { "_NET_SUPPORTED", F(atom.net_supported) }, + { "STRING", F(atom.string) }, + { "UTF8_STRING", F(atom.utf8_string) }, + { "CARDINAL", F(atom.cardinal) }, + { "_XKB_RULES_NAMES", F(atom.xkb_names) }, + }; + + xcb_intern_atom_cookie_t cookies[ARRAY_LENGTH(atoms)]; + xcb_intern_atom_reply_t *reply; + xcb_pixmap_t pixmap; + xcb_gc_t gc; + unsigned int i; + uint8_t data[] = { 0, 0, 0, 0 }; + + for (i = 0; i < ARRAY_LENGTH(atoms); i++) + cookies[i] = xcb_intern_atom (c->conn, 0, + strlen(atoms[i].name), + atoms[i].name); + + for (i = 0; i < ARRAY_LENGTH(atoms); i++) { + reply = xcb_intern_atom_reply (c->conn, cookies[i], NULL); + *(xcb_atom_t *) ((char *) c + atoms[i].offset) = reply->atom; + free(reply); + } + + pixmap = xcb_generate_id(c->conn); + gc = xcb_generate_id(c->conn); + xcb_create_pixmap(c->conn, 1, pixmap, c->screen->root, 1, 1); + xcb_create_gc(c->conn, gc, pixmap, 0, NULL); + xcb_put_image(c->conn, XCB_IMAGE_FORMAT_XY_PIXMAP, + pixmap, gc, 1, 1, 0, 0, 0, 32, sizeof data, data); + c->null_cursor = xcb_generate_id(c->conn); + xcb_create_cursor (c->conn, c->null_cursor, + pixmap, pixmap, 0, 0, 0, 0, 0, 0, 1, 1); + xcb_free_gc(c->conn, gc); + xcb_free_pixmap(c->conn, pixmap); +} + +static void +x11_compositor_get_wm_info(struct x11_compositor *c) +{ + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *reply; + xcb_atom_t *atom; + unsigned int i; + + cookie = xcb_get_property(c->conn, 0, c->screen->root, + c->atom.net_supported, + XCB_ATOM_ATOM, 0, 1024); + reply = xcb_get_property_reply(c->conn, cookie, NULL); + if (reply == NULL) + return; + + atom = (xcb_atom_t *) xcb_get_property_value(reply); + + for (i = 0; i < reply->value_len; i++) { + if (atom[i] == c->atom.net_wm_state_fullscreen) + c->has_net_wm_state_fullscreen = 1; + } +} + +static void +x11_restore(struct weston_compositor *ec) +{ +} + +static void +x11_destroy(struct weston_compositor *ec) +{ + struct x11_compositor *compositor = (struct x11_compositor *)ec; + + wl_event_source_remove(compositor->xcb_source); + x11_input_destroy(compositor); + + weston_compositor_shutdown(ec); /* destroys outputs, too */ + + ec->renderer->destroy(ec); + + XCloseDisplay(compositor->dpy); + free(ec); +} + +static uint32_t +parse_transform(const char *transform, const char *output_name) +{ + static const struct { const char *name; uint32_t token; } names[] = { + { "normal", WL_OUTPUT_TRANSFORM_NORMAL }, + { "90", WL_OUTPUT_TRANSFORM_90 }, + { "180", WL_OUTPUT_TRANSFORM_180 }, + { "270", WL_OUTPUT_TRANSFORM_270 }, + { "flipped", WL_OUTPUT_TRANSFORM_FLIPPED }, + { "flipped-90", WL_OUTPUT_TRANSFORM_FLIPPED_90 }, + { "flipped-180", WL_OUTPUT_TRANSFORM_FLIPPED_180 }, + { "flipped-270", WL_OUTPUT_TRANSFORM_FLIPPED_270 }, + }; + unsigned int i; + + for (i = 0; i < ARRAY_LENGTH(names); i++) + if (strcmp(names[i].name, transform) == 0) + return names[i].token; + + weston_log("Invalid transform \"%s\" for output %s\n", + transform, output_name); + + return WL_OUTPUT_TRANSFORM_NORMAL; +} + +static struct weston_compositor * +x11_compositor_create(struct wl_display *display, + int fullscreen, + int no_input, + int use_pixman, + int *argc, char *argv[], + struct weston_config *config) +{ + struct x11_compositor *c; + struct x11_output *output; + struct weston_config_section *section; + xcb_screen_iterator_t s; + int i, x = 0, output_count = 0; + int width, height, count, scale; + const char *section_name; + char *name, *t, *mode; + uint32_t transform; + + weston_log("initializing x11 backend\n"); + + c = zalloc(sizeof *c); + if (c == NULL) + return NULL; + + if (weston_compositor_init(&c->base, display, argc, argv, config) < 0) + goto err_free; + + c->dpy = XOpenDisplay(NULL); + if (c->dpy == NULL) + goto err_free; + + c->conn = XGetXCBConnection(c->dpy); + XSetEventQueueOwner(c->dpy, XCBOwnsEventQueue); + + if (xcb_connection_has_error(c->conn)) + goto err_xdisplay; + + s = xcb_setup_roots_iterator(xcb_get_setup(c->conn)); + c->screen = s.data; + wl_array_init(&c->keys); + + x11_compositor_get_resources(c); + x11_compositor_get_wm_info(c); + + if (!c->has_net_wm_state_fullscreen && fullscreen) { + weston_log("Can not fullscreen without window manager support" + "(need _NET_WM_STATE_FULLSCREEN)\n"); + fullscreen = 0; + } + + c->base.wl_display = display; + c->use_pixman = use_pixman; + if (c->use_pixman) { + if (pixman_renderer_init(&c->base) < 0) + goto err_xdisplay; + } + else { + if (gl_renderer_create(&c->base, (EGLNativeDisplayType)c->dpy, gl_renderer_opaque_attribs, + NULL) < 0) + goto err_xdisplay; + } + weston_log("Using %s renderer\n", use_pixman ? "pixman" : "gl"); + + c->base.destroy = x11_destroy; + c->base.restore = x11_restore; + + if (x11_input_create(c, no_input) < 0) + goto err_renderer; + + width = option_width ? option_width : 1024; + height = option_height ? option_height : 640; + count = option_count ? option_count : 1; + + section = NULL; + while (weston_config_next_section(c->base.config, + §ion, §ion_name)) { + if (strcmp(section_name, "output") != 0) + continue; + weston_config_section_get_string(section, "name", &name, NULL); + if (name == NULL || name[0] != 'X') { + free(name); + continue; + } + + weston_config_section_get_string(section, + "mode", &mode, "1024x600"); + if (sscanf(mode, "%dx%d", &width, &height) != 2) { + weston_log("Invalid mode \"%s\" for output %s\n", + mode, name); + width = 1024; + height = 600; + } + free(mode); + + if (option_width) + width = option_width; + if (option_height) + height = option_height; + + weston_config_section_get_int(section, "scale", &scale, 1); + weston_config_section_get_string(section, + "transform", &t, "normal"); + transform = parse_transform(t, name); + free(t); + + output = x11_compositor_create_output(c, x, 0, + width, height, + fullscreen, no_input, + name, transform, scale); + free(name); + if (output == NULL) + goto err_x11_input; + + x = pixman_region32_extents(&output->base.region)->x2; + + output_count++; + if (option_count && output_count >= option_count) + break; + } + + for (i = output_count; i < count; i++) { + output = x11_compositor_create_output(c, x, 0, width, height, + fullscreen, no_input, NULL, + WL_OUTPUT_TRANSFORM_NORMAL, 1); + if (output == NULL) + goto err_x11_input; + x = pixman_region32_extents(&output->base.region)->x2; + } + + c->xcb_source = + wl_event_loop_add_fd(c->base.input_loop, + xcb_get_file_descriptor(c->conn), + WL_EVENT_READABLE, + x11_compositor_handle_event, c); + wl_event_source_check(c->xcb_source); + + return &c->base; + +err_x11_input: + x11_input_destroy(c); +err_renderer: + c->base.renderer->destroy(&c->base); +err_xdisplay: + XCloseDisplay(c->dpy); +err_free: + free(c); + return NULL; +} + +WL_EXPORT struct weston_compositor * +backend_init(struct wl_display *display, int *argc, char *argv[], + struct weston_config *config) +{ + int fullscreen = 0; + int no_input = 0; + int use_pixman = 0; + + const struct weston_option x11_options[] = { + { WESTON_OPTION_INTEGER, "width", 0, &option_width }, + { WESTON_OPTION_INTEGER, "height", 0, &option_height }, + { WESTON_OPTION_BOOLEAN, "fullscreen", 'f', &fullscreen }, + { WESTON_OPTION_INTEGER, "output-count", 0, &option_count }, + { WESTON_OPTION_BOOLEAN, "no-input", 0, &no_input }, + { WESTON_OPTION_BOOLEAN, "use-pixman", 0, &use_pixman }, + }; + + parse_options(x11_options, ARRAY_LENGTH(x11_options), argc, argv); + + return x11_compositor_create(display, + fullscreen, + no_input, + use_pixman, + argc, argv, config); +} diff --git a/src/compositor.c b/src/compositor.c new file mode 100644 index 00000000..36b54b5b --- /dev/null +++ b/src/compositor.c @@ -0,0 +1,3595 @@ +/* + * Copyright © 2010-2011 Intel Corporation + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2012 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBUNWIND +#define UNW_LOCAL_ONLY +#include +#endif + +#include "compositor.h" +#include "subsurface-server-protocol.h" +#include "../shared/os-compatibility.h" +#include "git-version.h" +#include "version.h" + +static struct wl_list child_process_list; +static struct weston_compositor *segv_compositor; + +static int +sigchld_handler(int signal_number, void *data) +{ + struct weston_process *p; + int status; + pid_t pid; + + pid = waitpid(-1, &status, WNOHANG); + if (!pid) + return 1; + + wl_list_for_each(p, &child_process_list, link) { + if (p->pid == pid) + break; + } + + if (&p->link == &child_process_list) { + weston_log("unknown child process exited\n"); + return 1; + } + + wl_list_remove(&p->link); + p->cleanup(p, status); + + return 1; +} + +static void +weston_output_transform_scale_init(struct weston_output *output, + uint32_t transform, uint32_t scale); + +static void +weston_compositor_build_surface_list(struct weston_compositor *compositor); + +WL_EXPORT int +weston_output_switch_mode(struct weston_output *output, struct weston_mode *mode, + int32_t scale, enum weston_mode_switch_op op) +{ + struct weston_seat *seat; + struct wl_resource *resource; + pixman_region32_t old_output_region; + int ret, notify_mode_changed, notify_scale_changed; + int temporary_mode, temporary_scale; + + if (!output->switch_mode) + return -1; + + temporary_mode = (output->original_mode != 0); + temporary_scale = (output->current_scale != output->original_scale); + ret = 0; + + notify_mode_changed = 0; + notify_scale_changed = 0; + switch(op) { + case WESTON_MODE_SWITCH_SET_NATIVE: + output->native_mode = mode; + if (!temporary_mode) { + notify_mode_changed = 1; + ret = output->switch_mode(output, mode); + if (ret < 0) + return ret; + } + + output->native_scale = scale; + if(!temporary_scale) + notify_scale_changed = 1; + break; + case WESTON_MODE_SWITCH_SET_TEMPORARY: + if (!temporary_mode) + output->original_mode = output->native_mode; + if (!temporary_scale) + output->original_scale = output->native_scale; + + ret = output->switch_mode(output, mode); + if (ret < 0) + return ret; + + output->current_mode = mode; + output->current_scale = scale; + break; + case WESTON_MODE_SWITCH_RESTORE_NATIVE: + if (!temporary_mode) { + weston_log("already in the native mode\n"); + return -1; + } + + notify_mode_changed = (output->original_mode != output->native_mode); + + ret = output->switch_mode(output, mode); + if (ret < 0) + return ret; + + if (output->original_scale != output->native_scale) { + notify_scale_changed = 1; + scale = output->native_scale; + output->original_scale = scale; + } + output->original_mode = 0; + + output->current_scale = output->native_scale; + break; + default: + weston_log("unknown weston_switch_mode_op %d\n", op); + break; + } + + pixman_region32_init(&old_output_region); + pixman_region32_copy(&old_output_region, &output->region); + + /* Update output region and transformation matrix */ + weston_output_transform_scale_init(output, output->transform, output->current_scale); + + pixman_region32_init(&output->previous_damage); + pixman_region32_init_rect(&output->region, output->x, output->y, + output->width, output->height); + + weston_output_update_matrix(output); + + /* If a pointer falls outside the outputs new geometry, move it to its + * lower-right corner */ + wl_list_for_each(seat, &output->compositor->seat_list, link) { + struct weston_pointer *pointer = seat->pointer; + int32_t x, y; + + if (!pointer) + continue; + + x = wl_fixed_to_int(pointer->x); + y = wl_fixed_to_int(pointer->y); + + if (!pixman_region32_contains_point(&old_output_region, + x, y, NULL) || + pixman_region32_contains_point(&output->region, + x, y, NULL)) + continue; + + if (x >= output->x + output->width) + x = output->x + output->width - 1; + if (y >= output->y + output->height) + y = output->y + output->height - 1; + + pointer->x = wl_fixed_from_int(x); + pointer->y = wl_fixed_from_int(y); + } + + pixman_region32_fini(&old_output_region); + + /* notify clients of the changes */ + if (notify_mode_changed || notify_scale_changed) { + wl_resource_for_each(resource, &output->resource_list) { + if(notify_mode_changed) { + wl_output_send_mode(resource, + mode->flags | WL_OUTPUT_MODE_CURRENT, + mode->width, + mode->height, + mode->refresh); + } + + if (notify_scale_changed) + wl_output_send_scale(resource, scale); + + if (wl_resource_get_version(resource) >= 2) + wl_output_send_done(resource); + } + } + + return ret; +} + +WL_EXPORT void +weston_watch_process(struct weston_process *process) +{ + wl_list_insert(&child_process_list, &process->link); +} + +static void +child_client_exec(int sockfd, const char *path) +{ + int clientfd; + char s[32]; + sigset_t allsigs; + + /* do not give our signal mask to the new process */ + sigfillset(&allsigs); + sigprocmask(SIG_UNBLOCK, &allsigs, NULL); + + /* Launch clients as the user. Do not lauch clients with wrong euid.*/ + if (seteuid(getuid()) == -1) { + weston_log("compositor: failed seteuid\n"); + return; + } + + /* SOCK_CLOEXEC closes both ends, so we dup the fd to get a + * non-CLOEXEC fd to pass through exec. */ + clientfd = dup(sockfd); + if (clientfd == -1) { + weston_log("compositor: dup failed: %m\n"); + return; + } + + snprintf(s, sizeof s, "%d", clientfd); + setenv("WAYLAND_SOCKET", s, 1); + + if (execl(path, path, NULL) < 0) + weston_log("compositor: executing '%s' failed: %m\n", + path); +} + +WL_EXPORT struct wl_client * +weston_client_launch(struct weston_compositor *compositor, + struct weston_process *proc, + const char *path, + weston_process_cleanup_func_t cleanup) +{ + int sv[2]; + pid_t pid; + struct wl_client *client; + + weston_log("launching '%s'\n", path); + + if (os_socketpair_cloexec(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { + weston_log("weston_client_launch: " + "socketpair failed while launching '%s': %m\n", + path); + return NULL; + } + + pid = fork(); + if (pid == -1) { + close(sv[0]); + close(sv[1]); + weston_log("weston_client_launch: " + "fork failed while launching '%s': %m\n", path); + return NULL; + } + + if (pid == 0) { + child_client_exec(sv[1], path); + _exit(-1); + } + + close(sv[1]); + + client = wl_client_create(compositor->wl_display, sv[0]); + if (!client) { + close(sv[0]); + weston_log("weston_client_launch: " + "wl_client_create failed while launching '%s'.\n", + path); + return NULL; + } + + proc->pid = pid; + proc->cleanup = cleanup; + weston_watch_process(proc); + + return client; +} + +static void +surface_handle_pending_buffer_destroy(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = + container_of(listener, struct weston_surface, + pending.buffer_destroy_listener); + + surface->pending.buffer = NULL; +} + +static void +empty_region(pixman_region32_t *region) +{ + pixman_region32_fini(region); + pixman_region32_init(region); +} + +static void +region_init_infinite(pixman_region32_t *region) +{ + pixman_region32_init_rect(region, INT32_MIN, INT32_MIN, + UINT32_MAX, UINT32_MAX); +} + +static struct weston_subsurface * +weston_surface_to_subsurface(struct weston_surface *surface); + +WL_EXPORT struct weston_surface * +weston_surface_create(struct weston_compositor *compositor) +{ + struct weston_surface *surface; + + surface = calloc(1, sizeof *surface); + if (surface == NULL) + return NULL; + + wl_signal_init(&surface->destroy_signal); + + surface->resource = NULL; + + wl_list_init(&surface->link); + wl_list_init(&surface->layer_link); + + surface->compositor = compositor; + surface->alpha = 1.0; + surface->ref_count = 1; + + if (compositor->renderer->create_surface(surface) < 0) { + free(surface); + return NULL; + } + + surface->buffer_transform = WL_OUTPUT_TRANSFORM_NORMAL; + surface->buffer_scale = 1; + surface->pending.buffer_transform = surface->buffer_transform; + surface->pending.buffer_scale = surface->buffer_scale; + surface->output = NULL; + surface->plane = NULL; + surface->pending.newly_attached = 0; + + pixman_region32_init(&surface->damage); + pixman_region32_init(&surface->opaque); + pixman_region32_init(&surface->clip); + region_init_infinite(&surface->input); + pixman_region32_init(&surface->transform.opaque); + wl_list_init(&surface->frame_callback_list); + + wl_list_init(&surface->geometry.transformation_list); + wl_list_insert(&surface->geometry.transformation_list, + &surface->transform.position.link); + weston_matrix_init(&surface->transform.position.matrix); + wl_list_init(&surface->geometry.child_list); + pixman_region32_init(&surface->transform.boundingbox); + surface->transform.dirty = 1; + + surface->pending.buffer_destroy_listener.notify = + surface_handle_pending_buffer_destroy; + pixman_region32_init(&surface->pending.damage); + pixman_region32_init(&surface->pending.opaque); + region_init_infinite(&surface->pending.input); + wl_list_init(&surface->pending.frame_callback_list); + + wl_list_init(&surface->subsurface_list); + wl_list_init(&surface->subsurface_list_pending); + + return surface; +} + +WL_EXPORT void +weston_surface_set_color(struct weston_surface *surface, + float red, float green, float blue, float alpha) +{ + surface->compositor->renderer->surface_set_color(surface, red, green, blue, alpha); +} + +WL_EXPORT void +weston_surface_to_global_float(struct weston_surface *surface, + float sx, float sy, float *x, float *y) +{ + if (surface->transform.enabled) { + struct weston_vector v = { { sx, sy, 0.0f, 1.0f } }; + + weston_matrix_transform(&surface->transform.matrix, &v); + + if (fabsf(v.f[3]) < 1e-6) { + weston_log("warning: numerical instability in " + "%s(), divisor = %g\n", __func__, + v.f[3]); + *x = 0; + *y = 0; + return; + } + + *x = v.f[0] / v.f[3]; + *y = v.f[1] / v.f[3]; + } else { + *x = sx + surface->geometry.x; + *y = sy + surface->geometry.y; + } +} + +WL_EXPORT void +weston_transformed_coord(int width, int height, + enum wl_output_transform transform, + int32_t scale, + float sx, float sy, float *bx, float *by) +{ + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + default: + *bx = sx; + *by = sy; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + *bx = width - sx; + *by = sy; + break; + case WL_OUTPUT_TRANSFORM_90: + *bx = height - sy; + *by = sx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + *bx = height - sy; + *by = width - sx; + break; + case WL_OUTPUT_TRANSFORM_180: + *bx = width - sx; + *by = height - sy; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + *bx = sx; + *by = height - sy; + break; + case WL_OUTPUT_TRANSFORM_270: + *bx = sy; + *by = width - sx; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + *bx = sy; + *by = sx; + break; + } + + *bx *= scale; + *by *= scale; +} + +WL_EXPORT pixman_box32_t +weston_transformed_rect(int width, int height, + enum wl_output_transform transform, + int32_t scale, + pixman_box32_t rect) +{ + float x1, x2, y1, y2; + + pixman_box32_t ret; + + weston_transformed_coord(width, height, transform, scale, + rect.x1, rect.y1, &x1, &y1); + weston_transformed_coord(width, height, transform, scale, + rect.x2, rect.y2, &x2, &y2); + + if (x1 <= x2) { + ret.x1 = x1; + ret.x2 = x2; + } else { + ret.x1 = x2; + ret.x2 = x1; + } + + if (y1 <= y2) { + ret.y1 = y1; + ret.y2 = y2; + } else { + ret.y1 = y2; + ret.y2 = y1; + } + + return ret; +} + +WL_EXPORT void +weston_surface_to_buffer_float(struct weston_surface *surface, + float sx, float sy, float *bx, float *by) +{ + weston_transformed_coord(surface->geometry.width, + surface->geometry.height, + surface->buffer_transform, + surface->buffer_scale, + sx, sy, bx, by); +} + +WL_EXPORT void +weston_surface_to_buffer(struct weston_surface *surface, + int sx, int sy, int *bx, int *by) +{ + float bxf, byf; + + weston_transformed_coord(surface->geometry.width, + surface->geometry.height, + surface->buffer_transform, + surface->buffer_scale, + sx, sy, &bxf, &byf); + *bx = floorf(bxf); + *by = floorf(byf); +} + +WL_EXPORT pixman_box32_t +weston_surface_to_buffer_rect(struct weston_surface *surface, + pixman_box32_t rect) +{ + return weston_transformed_rect(surface->geometry.width, + surface->geometry.height, + surface->buffer_transform, + surface->buffer_scale, + rect); +} + +WL_EXPORT void +weston_surface_move_to_plane(struct weston_surface *surface, + struct weston_plane *plane) +{ + if (surface->plane == plane) + return; + + weston_surface_damage_below(surface); + surface->plane = plane; + weston_surface_damage(surface); +} + +WL_EXPORT void +weston_surface_damage_below(struct weston_surface *surface) +{ + pixman_region32_t damage; + + pixman_region32_init(&damage); + pixman_region32_subtract(&damage, &surface->transform.boundingbox, + &surface->clip); + if (surface->plane) + pixman_region32_union(&surface->plane->damage, + &surface->plane->damage, &damage); + pixman_region32_fini(&damage); +} + +static void +weston_surface_update_output_mask(struct weston_surface *es, uint32_t mask) +{ + uint32_t different = es->output_mask ^ mask; + uint32_t entered = mask & different; + uint32_t left = es->output_mask & different; + struct weston_output *output; + struct wl_resource *resource = NULL; + struct wl_client *client; + + es->output_mask = mask; + if (es->resource == NULL) + return; + if (different == 0) + return; + + client = wl_resource_get_client(es->resource); + + wl_list_for_each(output, &es->compositor->output_list, link) { + if (1 << output->id & different) + resource = + wl_resource_find_for_client(&output->resource_list, + client); + if (resource == NULL) + continue; + if (1 << output->id & entered) + wl_surface_send_enter(es->resource, resource); + if (1 << output->id & left) + wl_surface_send_leave(es->resource, resource); + } +} + +static void +weston_surface_assign_output(struct weston_surface *es) +{ + struct weston_compositor *ec = es->compositor; + struct weston_output *output, *new_output; + pixman_region32_t region; + uint32_t max, area, mask; + pixman_box32_t *e; + + new_output = NULL; + max = 0; + mask = 0; + pixman_region32_init(®ion); + wl_list_for_each(output, &ec->output_list, link) { + pixman_region32_intersect(®ion, &es->transform.boundingbox, + &output->region); + + e = pixman_region32_extents(®ion); + area = (e->x2 - e->x1) * (e->y2 - e->y1); + + if (area > 0) + mask |= 1 << output->id; + + if (area >= max) { + new_output = output; + max = area; + } + } + pixman_region32_fini(®ion); + + es->output = new_output; + weston_surface_update_output_mask(es, mask); +} + +static void +surface_compute_bbox(struct weston_surface *surface, int32_t sx, int32_t sy, + int32_t width, int32_t height, + pixman_region32_t *bbox) +{ + float min_x = HUGE_VALF, min_y = HUGE_VALF; + float max_x = -HUGE_VALF, max_y = -HUGE_VALF; + int32_t s[4][2] = { + { sx, sy }, + { sx, sy + height }, + { sx + width, sy }, + { sx + width, sy + height } + }; + float int_x, int_y; + int i; + + if (width == 0 || height == 0) { + /* avoid rounding empty bbox to 1x1 */ + pixman_region32_init(bbox); + return; + } + + for (i = 0; i < 4; ++i) { + float x, y; + weston_surface_to_global_float(surface, + s[i][0], s[i][1], &x, &y); + if (x < min_x) + min_x = x; + if (x > max_x) + max_x = x; + if (y < min_y) + min_y = y; + if (y > max_y) + max_y = y; + } + + int_x = floorf(min_x); + int_y = floorf(min_y); + pixman_region32_init_rect(bbox, int_x, int_y, + ceilf(max_x) - int_x, ceilf(max_y) - int_y); +} + +static void +weston_surface_update_transform_disable(struct weston_surface *surface) +{ + surface->transform.enabled = 0; + + /* round off fractions when not transformed */ + surface->geometry.x = roundf(surface->geometry.x); + surface->geometry.y = roundf(surface->geometry.y); + + /* Otherwise identity matrix, but with x and y translation. */ + surface->transform.position.matrix.type = WESTON_MATRIX_TRANSFORM_TRANSLATE; + surface->transform.position.matrix.d[12] = surface->geometry.x; + surface->transform.position.matrix.d[13] = surface->geometry.y; + + surface->transform.matrix = surface->transform.position.matrix; + + surface->transform.inverse = surface->transform.position.matrix; + surface->transform.inverse.d[12] = -surface->geometry.x; + surface->transform.inverse.d[13] = -surface->geometry.y; + + pixman_region32_init_rect(&surface->transform.boundingbox, + surface->geometry.x, + surface->geometry.y, + surface->geometry.width, + surface->geometry.height); + + if (surface->alpha == 1.0) { + pixman_region32_copy(&surface->transform.opaque, + &surface->opaque); + pixman_region32_translate(&surface->transform.opaque, + surface->geometry.x, + surface->geometry.y); + } +} + +static int +weston_surface_update_transform_enable(struct weston_surface *surface) +{ + struct weston_surface *parent = surface->geometry.parent; + struct weston_matrix *matrix = &surface->transform.matrix; + struct weston_matrix *inverse = &surface->transform.inverse; + struct weston_transform *tform; + + surface->transform.enabled = 1; + + /* Otherwise identity matrix, but with x and y translation. */ + surface->transform.position.matrix.type = WESTON_MATRIX_TRANSFORM_TRANSLATE; + surface->transform.position.matrix.d[12] = surface->geometry.x; + surface->transform.position.matrix.d[13] = surface->geometry.y; + + weston_matrix_init(matrix); + wl_list_for_each(tform, &surface->geometry.transformation_list, link) + weston_matrix_multiply(matrix, &tform->matrix); + + if (parent) + weston_matrix_multiply(matrix, &parent->transform.matrix); + + if (weston_matrix_invert(inverse, matrix) < 0) { + /* Oops, bad total transformation, not invertible */ + weston_log("error: weston_surface %p" + " transformation not invertible.\n", surface); + return -1; + } + + surface_compute_bbox(surface, 0, 0, surface->geometry.width, + surface->geometry.height, + &surface->transform.boundingbox); + + return 0; +} + +WL_EXPORT void +weston_surface_update_transform(struct weston_surface *surface) +{ + struct weston_surface *parent = surface->geometry.parent; + + if (!surface->transform.dirty) + return; + + if (parent) + weston_surface_update_transform(parent); + + surface->transform.dirty = 0; + + weston_surface_damage_below(surface); + + pixman_region32_fini(&surface->transform.boundingbox); + pixman_region32_fini(&surface->transform.opaque); + pixman_region32_init(&surface->transform.opaque); + + /* transform.position is always in transformation_list */ + if (surface->geometry.transformation_list.next == + &surface->transform.position.link && + surface->geometry.transformation_list.prev == + &surface->transform.position.link && + !parent) { + weston_surface_update_transform_disable(surface); + } else { + if (weston_surface_update_transform_enable(surface) < 0) + weston_surface_update_transform_disable(surface); + } + + weston_surface_damage_below(surface); + + weston_surface_assign_output(surface); + + wl_signal_emit(&surface->compositor->transform_signal, surface); +} + +WL_EXPORT void +weston_surface_geometry_dirty(struct weston_surface *surface) +{ + struct weston_surface *child; + + /* + * The invariant: if surface->geometry.dirty, then all surfaces + * in surface->geometry.child_list have geometry.dirty too. + * Corollary: if not parent->geometry.dirty, then all ancestors + * are not dirty. + */ + + if (surface->transform.dirty) + return; + + surface->transform.dirty = 1; + + wl_list_for_each(child, &surface->geometry.child_list, + geometry.parent_link) + weston_surface_geometry_dirty(child); +} + +WL_EXPORT void +weston_surface_to_global_fixed(struct weston_surface *surface, + wl_fixed_t sx, wl_fixed_t sy, + wl_fixed_t *x, wl_fixed_t *y) +{ + float xf, yf; + + weston_surface_to_global_float(surface, + wl_fixed_to_double(sx), + wl_fixed_to_double(sy), + &xf, &yf); + *x = wl_fixed_from_double(xf); + *y = wl_fixed_from_double(yf); +} + +WL_EXPORT void +weston_surface_from_global_float(struct weston_surface *surface, + float x, float y, float *sx, float *sy) +{ + if (surface->transform.enabled) { + struct weston_vector v = { { x, y, 0.0f, 1.0f } }; + + weston_matrix_transform(&surface->transform.inverse, &v); + + if (fabsf(v.f[3]) < 1e-6) { + weston_log("warning: numerical instability in " + "weston_surface_from_global(), divisor = %g\n", + v.f[3]); + *sx = 0; + *sy = 0; + return; + } + + *sx = v.f[0] / v.f[3]; + *sy = v.f[1] / v.f[3]; + } else { + *sx = x - surface->geometry.x; + *sy = y - surface->geometry.y; + } +} + +WL_EXPORT void +weston_surface_from_global_fixed(struct weston_surface *surface, + wl_fixed_t x, wl_fixed_t y, + wl_fixed_t *sx, wl_fixed_t *sy) +{ + float sxf, syf; + + weston_surface_from_global_float(surface, + wl_fixed_to_double(x), + wl_fixed_to_double(y), + &sxf, &syf); + *sx = wl_fixed_from_double(sxf); + *sy = wl_fixed_from_double(syf); +} + +WL_EXPORT void +weston_surface_from_global(struct weston_surface *surface, + int32_t x, int32_t y, int32_t *sx, int32_t *sy) +{ + float sxf, syf; + + weston_surface_from_global_float(surface, x, y, &sxf, &syf); + *sx = floorf(sxf); + *sy = floorf(syf); +} + +WL_EXPORT void +weston_surface_schedule_repaint(struct weston_surface *surface) +{ + struct weston_output *output; + + wl_list_for_each(output, &surface->compositor->output_list, link) + if (surface->output_mask & (1 << output->id)) + weston_output_schedule_repaint(output); +} + +WL_EXPORT void +weston_surface_damage(struct weston_surface *surface) +{ + pixman_region32_union_rect(&surface->damage, &surface->damage, + 0, 0, surface->geometry.width, + surface->geometry.height); + + weston_surface_schedule_repaint(surface); +} + +WL_EXPORT void +weston_surface_configure(struct weston_surface *surface, + float x, float y, int width, int height) +{ + surface->geometry.x = x; + surface->geometry.y = y; + surface->geometry.width = width; + surface->geometry.height = height; + weston_surface_geometry_dirty(surface); +} + +WL_EXPORT void +weston_surface_set_position(struct weston_surface *surface, + float x, float y) +{ + surface->geometry.x = x; + surface->geometry.y = y; + weston_surface_geometry_dirty(surface); +} + +static void +transform_parent_handle_parent_destroy(struct wl_listener *listener, + void *data) +{ + struct weston_surface *surface = + container_of(listener, struct weston_surface, + geometry.parent_destroy_listener); + + weston_surface_set_transform_parent(surface, NULL); +} + +WL_EXPORT void +weston_surface_set_transform_parent(struct weston_surface *surface, + struct weston_surface *parent) +{ + if (surface->geometry.parent) { + wl_list_remove(&surface->geometry.parent_destroy_listener.link); + wl_list_remove(&surface->geometry.parent_link); + } + + surface->geometry.parent = parent; + + surface->geometry.parent_destroy_listener.notify = + transform_parent_handle_parent_destroy; + if (parent) { + wl_signal_add(&parent->destroy_signal, + &surface->geometry.parent_destroy_listener); + wl_list_insert(&parent->geometry.child_list, + &surface->geometry.parent_link); + } + + weston_surface_geometry_dirty(surface); +} + +WL_EXPORT int +weston_surface_is_mapped(struct weston_surface *surface) +{ + if (surface->output) + return 1; + else + return 0; +} + +WL_EXPORT int32_t +weston_surface_buffer_width(struct weston_surface *surface) +{ + int32_t width; + switch (surface->buffer_transform) { + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + width = surface->buffer_ref.buffer->height; + break; + default: + width = surface->buffer_ref.buffer->width; + break; + } + return width / surface->buffer_scale; +} + +WL_EXPORT int32_t +weston_surface_buffer_height(struct weston_surface *surface) +{ + int32_t height; + switch (surface->buffer_transform) { + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + height = surface->buffer_ref.buffer->width; + break; + default: + height = surface->buffer_ref.buffer->height; + break; + } + return height / surface->buffer_scale; +} + +WL_EXPORT uint32_t +weston_compositor_get_time(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +WL_EXPORT struct weston_surface * +weston_compositor_pick_surface(struct weston_compositor *compositor, + wl_fixed_t x, wl_fixed_t y, + wl_fixed_t *sx, wl_fixed_t *sy) +{ + struct weston_surface *surface; + + wl_list_for_each(surface, &compositor->surface_list, link) { + weston_surface_from_global_fixed(surface, x, y, sx, sy); + if (pixman_region32_contains_point(&surface->input, + wl_fixed_to_int(*sx), + wl_fixed_to_int(*sy), + NULL)) + return surface; + } + + return NULL; +} + +static void +weston_compositor_repick(struct weston_compositor *compositor) +{ + struct weston_seat *seat; + + if (!compositor->focus) + return; + + wl_list_for_each(seat, &compositor->seat_list, link) + weston_seat_repick(seat); +} + +WL_EXPORT void +weston_surface_unmap(struct weston_surface *surface) +{ + struct weston_seat *seat; + + weston_surface_damage_below(surface); + surface->output = NULL; + surface->plane = NULL; + wl_list_remove(&surface->layer_link); + wl_list_remove(&surface->link); + wl_list_init(&surface->link); + + wl_list_for_each(seat, &surface->compositor->seat_list, link) { + if (seat->keyboard && seat->keyboard->focus == surface) + weston_keyboard_set_focus(seat->keyboard, NULL); + if (seat->pointer && seat->pointer->focus == surface) + weston_pointer_set_focus(seat->pointer, + NULL, + wl_fixed_from_int(0), + wl_fixed_from_int(0)); + if (seat->touch && seat->touch->focus == surface) + weston_touch_set_focus(seat, NULL); + } + + weston_surface_schedule_repaint(surface); +} + +struct weston_frame_callback { + struct wl_resource *resource; + struct wl_list link; +}; + + +WL_EXPORT void +weston_surface_destroy(struct weston_surface *surface) +{ + struct weston_compositor *compositor = surface->compositor; + struct weston_frame_callback *cb, *next; + + if (--surface->ref_count > 0) + return; + + wl_signal_emit(&surface->destroy_signal, &surface->resource); + + assert(wl_list_empty(&surface->geometry.child_list)); + assert(wl_list_empty(&surface->subsurface_list_pending)); + assert(wl_list_empty(&surface->subsurface_list)); + + if (weston_surface_is_mapped(surface)) + weston_surface_unmap(surface); + + wl_list_for_each_safe(cb, next, + &surface->pending.frame_callback_list, link) + wl_resource_destroy(cb->resource); + + pixman_region32_fini(&surface->pending.input); + pixman_region32_fini(&surface->pending.opaque); + pixman_region32_fini(&surface->pending.damage); + + if (surface->pending.buffer) + wl_list_remove(&surface->pending.buffer_destroy_listener.link); + + weston_buffer_reference(&surface->buffer_ref, NULL); + + compositor->renderer->destroy_surface(surface); + + pixman_region32_fini(&surface->transform.boundingbox); + pixman_region32_fini(&surface->damage); + pixman_region32_fini(&surface->opaque); + pixman_region32_fini(&surface->clip); + pixman_region32_fini(&surface->input); + + wl_list_for_each_safe(cb, next, &surface->frame_callback_list, link) + wl_resource_destroy(cb->resource); + + weston_surface_set_transform_parent(surface, NULL); + + free(surface); +} + +static void +destroy_surface(struct wl_resource *resource) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + + weston_surface_destroy(surface); +} + +static void +weston_buffer_destroy_handler(struct wl_listener *listener, void *data) +{ + struct weston_buffer *buffer = + container_of(listener, struct weston_buffer, destroy_listener); + + wl_signal_emit(&buffer->destroy_signal, buffer); + free(buffer); +} + +struct weston_buffer * +weston_buffer_from_resource(struct wl_resource *resource) +{ + struct weston_buffer *buffer; + struct wl_listener *listener; + + listener = wl_resource_get_destroy_listener(resource, + weston_buffer_destroy_handler); + + if (listener) + return container_of(listener, struct weston_buffer, + destroy_listener); + + buffer = zalloc(sizeof *buffer); + if (buffer == NULL) + return NULL; + + buffer->resource = resource; + wl_signal_init(&buffer->destroy_signal); + buffer->destroy_listener.notify = weston_buffer_destroy_handler; + buffer->y_inverted = 1; + wl_resource_add_destroy_listener(resource, &buffer->destroy_listener); + + return buffer; +} + +static void +weston_buffer_reference_handle_destroy(struct wl_listener *listener, + void *data) +{ + struct weston_buffer_reference *ref = + container_of(listener, struct weston_buffer_reference, + destroy_listener); + + assert((struct weston_buffer *)data == ref->buffer); + ref->buffer = NULL; +} + +WL_EXPORT void +weston_buffer_reference(struct weston_buffer_reference *ref, + struct weston_buffer *buffer) +{ + if (ref->buffer && buffer != ref->buffer) { + ref->buffer->busy_count--; + if (ref->buffer->busy_count == 0) { + assert(wl_resource_get_client(ref->buffer->resource)); + wl_resource_queue_event(ref->buffer->resource, + WL_BUFFER_RELEASE); + } + wl_list_remove(&ref->destroy_listener.link); + } + + if (buffer && buffer != ref->buffer) { + buffer->busy_count++; + wl_signal_add(&buffer->destroy_signal, + &ref->destroy_listener); + } + + ref->buffer = buffer; + ref->destroy_listener.notify = weston_buffer_reference_handle_destroy; +} + +static void +weston_surface_attach(struct weston_surface *surface, + struct weston_buffer *buffer) +{ + weston_buffer_reference(&surface->buffer_ref, buffer); + + if (!buffer) { + if (weston_surface_is_mapped(surface)) + weston_surface_unmap(surface); + } + + surface->compositor->renderer->attach(surface, buffer); +} + +WL_EXPORT void +weston_surface_restack(struct weston_surface *surface, struct wl_list *below) +{ + wl_list_remove(&surface->layer_link); + wl_list_insert(below, &surface->layer_link); + weston_surface_damage_below(surface); + weston_surface_damage(surface); +} + +WL_EXPORT void +weston_compositor_damage_all(struct weston_compositor *compositor) +{ + struct weston_output *output; + + wl_list_for_each(output, &compositor->output_list, link) + weston_output_damage(output); +} + +WL_EXPORT void +weston_output_damage(struct weston_output *output) +{ + struct weston_compositor *compositor = output->compositor; + + pixman_region32_union(&compositor->primary_plane.damage, + &compositor->primary_plane.damage, + &output->region); + weston_output_schedule_repaint(output); +} + +static void +surface_accumulate_damage(struct weston_surface *surface, + pixman_region32_t *opaque) +{ + if (surface->buffer_ref.buffer && + wl_shm_buffer_get(surface->buffer_ref.buffer->resource)) + surface->compositor->renderer->flush_damage(surface); + + if (surface->transform.enabled) { + pixman_box32_t *extents; + + extents = pixman_region32_extents(&surface->damage); + surface_compute_bbox(surface, extents->x1, extents->y1, + extents->x2 - extents->x1, + extents->y2 - extents->y1, + &surface->damage); + pixman_region32_translate(&surface->damage, + -surface->plane->x, + -surface->plane->y); + } else { + pixman_region32_translate(&surface->damage, + surface->geometry.x - surface->plane->x, + surface->geometry.y - surface->plane->y); + } + + pixman_region32_subtract(&surface->damage, &surface->damage, opaque); + pixman_region32_union(&surface->plane->damage, + &surface->plane->damage, &surface->damage); + empty_region(&surface->damage); + pixman_region32_copy(&surface->clip, opaque); + pixman_region32_union(opaque, opaque, &surface->transform.opaque); +} + +static void +compositor_accumulate_damage(struct weston_compositor *ec) +{ + struct weston_plane *plane; + struct weston_surface *es; + pixman_region32_t opaque, clip; + + pixman_region32_init(&clip); + + wl_list_for_each(plane, &ec->plane_list, link) { + pixman_region32_copy(&plane->clip, &clip); + + pixman_region32_init(&opaque); + + wl_list_for_each(es, &ec->surface_list, link) { + if (es->plane != plane) + continue; + + surface_accumulate_damage(es, &opaque); + } + + pixman_region32_union(&clip, &clip, &opaque); + pixman_region32_fini(&opaque); + } + + pixman_region32_fini(&clip); + + wl_list_for_each(es, &ec->surface_list, link) { + /* Both the renderer and the backend have seen the buffer + * by now. If renderer needs the buffer, it has its own + * reference set. If the backend wants to keep the buffer + * around for migrating the surface into a non-primary plane + * later, keep_buffer is true. Otherwise, drop the core + * reference now, and allow early buffer release. This enables + * clients to use single-buffering. + */ + if (!es->keep_buffer) + weston_buffer_reference(&es->buffer_ref, NULL); + } +} + +static void +surface_list_add(struct weston_compositor *compositor, + struct weston_surface *surface) +{ + struct weston_subsurface *sub; + + if (wl_list_empty(&surface->subsurface_list)) { + weston_surface_update_transform(surface); + wl_list_insert(compositor->surface_list.prev, &surface->link); + return; + } + + wl_list_for_each(sub, &surface->subsurface_list, parent_link) { + if (!weston_surface_is_mapped(sub->surface)) + continue; + + if (sub->surface == surface) { + weston_surface_update_transform(sub->surface); + wl_list_insert(compositor->surface_list.prev, + &sub->surface->link); + } else { + surface_list_add(compositor, sub->surface); + } + } +} + +static void +weston_compositor_build_surface_list(struct weston_compositor *compositor) +{ + struct weston_surface *surface; + struct weston_layer *layer; + + wl_list_init(&compositor->surface_list); + wl_list_for_each(layer, &compositor->layer_list, link) { + wl_list_for_each(surface, &layer->surface_list, layer_link) { + surface_list_add(compositor, surface); + } + } +} + +static int +weston_output_repaint(struct weston_output *output, uint32_t msecs) +{ + struct weston_compositor *ec = output->compositor; + struct weston_surface *es; + struct weston_animation *animation, *next; + struct weston_frame_callback *cb, *cnext; + struct wl_list frame_callback_list; + pixman_region32_t output_damage; + int r; + + /* Rebuild the surface list and update surface transforms up front. */ + weston_compositor_build_surface_list(ec); + + if (output->assign_planes && !output->disable_planes) + output->assign_planes(output); + else + wl_list_for_each(es, &ec->surface_list, link) + weston_surface_move_to_plane(es, &ec->primary_plane); + + wl_list_init(&frame_callback_list); + wl_list_for_each(es, &ec->surface_list, link) { + if (es->output == output) { + wl_list_insert_list(&frame_callback_list, + &es->frame_callback_list); + wl_list_init(&es->frame_callback_list); + } + } + + compositor_accumulate_damage(ec); + + pixman_region32_init(&output_damage); + pixman_region32_intersect(&output_damage, + &ec->primary_plane.damage, &output->region); + pixman_region32_subtract(&output_damage, + &output_damage, &ec->primary_plane.clip); + + if (output->dirty) + weston_output_update_matrix(output); + + r = output->repaint(output, &output_damage); + + pixman_region32_fini(&output_damage); + + output->repaint_needed = 0; + + weston_compositor_repick(ec); + wl_event_loop_dispatch(ec->input_loop, 0); + + wl_list_for_each_safe(cb, cnext, &frame_callback_list, link) { + wl_callback_send_done(cb->resource, msecs); + wl_resource_destroy(cb->resource); + } + + wl_list_for_each_safe(animation, next, &output->animation_list, link) { + animation->frame_counter++; + animation->frame(animation, output, msecs); + } + + return r; +} + +static int +weston_compositor_read_input(int fd, uint32_t mask, void *data) +{ + struct weston_compositor *compositor = data; + + wl_event_loop_dispatch(compositor->input_loop, 0); + + return 1; +} + +WL_EXPORT void +weston_output_finish_frame(struct weston_output *output, uint32_t msecs) +{ + struct weston_compositor *compositor = output->compositor; + struct wl_event_loop *loop = + wl_display_get_event_loop(compositor->wl_display); + int fd, r; + + output->frame_time = msecs; + + if (output->repaint_needed && + compositor->state != WESTON_COMPOSITOR_SLEEPING && + compositor->state != WESTON_COMPOSITOR_OFFSCREEN) { + r = weston_output_repaint(output, msecs); + if (!r) + return; + } + + output->repaint_scheduled = 0; + if (compositor->input_loop_source) + return; + + fd = wl_event_loop_get_fd(compositor->input_loop); + compositor->input_loop_source = + wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + weston_compositor_read_input, compositor); +} + +static void +idle_repaint(void *data) +{ + struct weston_output *output = data; + + output->start_repaint_loop(output); +} + +WL_EXPORT void +weston_layer_init(struct weston_layer *layer, struct wl_list *below) +{ + wl_list_init(&layer->surface_list); + if (below != NULL) + wl_list_insert(below, &layer->link); +} + +WL_EXPORT void +weston_output_schedule_repaint(struct weston_output *output) +{ + struct weston_compositor *compositor = output->compositor; + struct wl_event_loop *loop; + + if (compositor->state == WESTON_COMPOSITOR_SLEEPING || + compositor->state == WESTON_COMPOSITOR_OFFSCREEN) + return; + + loop = wl_display_get_event_loop(compositor->wl_display); + output->repaint_needed = 1; + if (output->repaint_scheduled) + return; + + wl_event_loop_add_idle(loop, idle_repaint, output); + output->repaint_scheduled = 1; + + if (compositor->input_loop_source) { + wl_event_source_remove(compositor->input_loop_source); + compositor->input_loop_source = NULL; + } +} + +WL_EXPORT void +weston_compositor_schedule_repaint(struct weston_compositor *compositor) +{ + struct weston_output *output; + + wl_list_for_each(output, &compositor->output_list, link) + weston_output_schedule_repaint(output); +} + +static void +surface_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +surface_attach(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *buffer_resource, int32_t sx, int32_t sy) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + struct weston_buffer *buffer = NULL; + + if (buffer_resource) { + buffer = weston_buffer_from_resource(buffer_resource); + if (buffer == NULL) { + wl_client_post_no_memory(client); + return; + } + } + + /* Attach, attach, without commit in between does not send + * wl_buffer.release. */ + if (surface->pending.buffer) + wl_list_remove(&surface->pending.buffer_destroy_listener.link); + + surface->pending.sx = sx; + surface->pending.sy = sy; + surface->pending.buffer = buffer; + surface->pending.newly_attached = 1; + if (buffer) { + wl_signal_add(&buffer->destroy_signal, + &surface->pending.buffer_destroy_listener); + } +} + +static void +surface_damage(struct wl_client *client, + struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + + pixman_region32_union_rect(&surface->pending.damage, + &surface->pending.damage, + x, y, width, height); +} + +static void +destroy_frame_callback(struct wl_resource *resource) +{ + struct weston_frame_callback *cb = wl_resource_get_user_data(resource); + + wl_list_remove(&cb->link); + free(cb); +} + +static void +surface_frame(struct wl_client *client, + struct wl_resource *resource, uint32_t callback) +{ + struct weston_frame_callback *cb; + struct weston_surface *surface = wl_resource_get_user_data(resource); + + cb = malloc(sizeof *cb); + if (cb == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + cb->resource = wl_resource_create(client, &wl_callback_interface, 1, + callback); + if (cb->resource == NULL) { + free(cb); + wl_resource_post_no_memory(resource); + return; + } + + wl_resource_set_implementation(cb->resource, NULL, cb, + destroy_frame_callback); + + wl_list_insert(surface->pending.frame_callback_list.prev, &cb->link); +} + +static void +surface_set_opaque_region(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region_resource) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + struct weston_region *region; + + if (region_resource) { + region = wl_resource_get_user_data(region_resource); + pixman_region32_copy(&surface->pending.opaque, + ®ion->region); + } else { + empty_region(&surface->pending.opaque); + } +} + +static void +surface_set_input_region(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *region_resource) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + struct weston_region *region; + + if (region_resource) { + region = wl_resource_get_user_data(region_resource); + pixman_region32_copy(&surface->pending.input, + ®ion->region); + } else { + pixman_region32_fini(&surface->pending.input); + region_init_infinite(&surface->pending.input); + } +} + +static void +weston_surface_commit_subsurface_order(struct weston_surface *surface) +{ + struct weston_subsurface *sub; + + wl_list_for_each_reverse(sub, &surface->subsurface_list_pending, + parent_link_pending) { + wl_list_remove(&sub->parent_link); + wl_list_insert(&surface->subsurface_list, &sub->parent_link); + } +} + +static void +weston_surface_commit(struct weston_surface *surface) +{ + pixman_region32_t opaque; + int surface_width = 0; + int surface_height = 0; + + /* wl_surface.set_buffer_transform */ + surface->buffer_transform = surface->pending.buffer_transform; + + /* wl_surface.set_buffer_scale */ + surface->buffer_scale = surface->pending.buffer_scale; + + /* wl_surface.attach */ + if (surface->pending.buffer || surface->pending.newly_attached) + weston_surface_attach(surface, surface->pending.buffer); + + if (surface->buffer_ref.buffer) { + surface_width = weston_surface_buffer_width(surface); + surface_height = weston_surface_buffer_height(surface); + } + + if (surface->configure && surface->pending.newly_attached) + surface->configure(surface, + surface->pending.sx, surface->pending.sy, + surface_width, surface_height); + + if (surface->pending.buffer) + wl_list_remove(&surface->pending.buffer_destroy_listener.link); + surface->pending.buffer = NULL; + surface->pending.sx = 0; + surface->pending.sy = 0; + surface->pending.newly_attached = 0; + + /* wl_surface.damage */ + pixman_region32_union(&surface->damage, &surface->damage, + &surface->pending.damage); + pixman_region32_intersect_rect(&surface->damage, &surface->damage, + 0, 0, + surface->geometry.width, + surface->geometry.height); + empty_region(&surface->pending.damage); + + /* wl_surface.set_opaque_region */ + pixman_region32_init_rect(&opaque, 0, 0, + surface->geometry.width, + surface->geometry.height); + pixman_region32_intersect(&opaque, + &opaque, &surface->pending.opaque); + + if (!pixman_region32_equal(&opaque, &surface->opaque)) { + pixman_region32_copy(&surface->opaque, &opaque); + weston_surface_geometry_dirty(surface); + } + + pixman_region32_fini(&opaque); + + /* wl_surface.set_input_region */ + pixman_region32_fini(&surface->input); + pixman_region32_init_rect(&surface->input, 0, 0, + surface->geometry.width, + surface->geometry.height); + pixman_region32_intersect(&surface->input, + &surface->input, &surface->pending.input); + + /* wl_surface.frame */ + wl_list_insert_list(&surface->frame_callback_list, + &surface->pending.frame_callback_list); + wl_list_init(&surface->pending.frame_callback_list); + + weston_surface_commit_subsurface_order(surface); + + weston_surface_schedule_repaint(surface); +} + +static void +weston_subsurface_commit(struct weston_subsurface *sub); + +static void +weston_subsurface_parent_commit(struct weston_subsurface *sub, + int parent_is_synchronized); + +static void +surface_commit(struct wl_client *client, struct wl_resource *resource) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + struct weston_subsurface *sub = weston_surface_to_subsurface(surface); + + if (sub) { + weston_subsurface_commit(sub); + return; + } + + weston_surface_commit(surface); + + wl_list_for_each(sub, &surface->subsurface_list, parent_link) { + if (sub->surface != surface) + weston_subsurface_parent_commit(sub, 0); + } +} + +static void +surface_set_buffer_transform(struct wl_client *client, + struct wl_resource *resource, int transform) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + + surface->pending.buffer_transform = transform; +} + +static void +surface_set_buffer_scale(struct wl_client *client, + struct wl_resource *resource, + int32_t scale) +{ + struct weston_surface *surface = wl_resource_get_user_data(resource); + + surface->pending.buffer_scale = scale; +} + +static const struct wl_surface_interface surface_interface = { + surface_destroy, + surface_attach, + surface_damage, + surface_frame, + surface_set_opaque_region, + surface_set_input_region, + surface_commit, + surface_set_buffer_transform, + surface_set_buffer_scale +}; + +static void +compositor_create_surface(struct wl_client *client, + struct wl_resource *resource, uint32_t id) +{ + struct weston_compositor *ec = wl_resource_get_user_data(resource); + struct weston_surface *surface; + + surface = weston_surface_create(ec); + if (surface == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + surface->resource = + wl_resource_create(client, &wl_surface_interface, + wl_resource_get_version(resource), id); + if (surface->resource == NULL) { + weston_surface_destroy(surface); + wl_resource_post_no_memory(resource); + return; + } + wl_resource_set_implementation(surface->resource, &surface_interface, + surface, destroy_surface); +} + +static void +destroy_region(struct wl_resource *resource) +{ + struct weston_region *region = wl_resource_get_user_data(resource); + + pixman_region32_fini(®ion->region); + free(region); +} + +static void +region_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +region_add(struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + struct weston_region *region = wl_resource_get_user_data(resource); + + pixman_region32_union_rect(®ion->region, ®ion->region, + x, y, width, height); +} + +static void +region_subtract(struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + struct weston_region *region = wl_resource_get_user_data(resource); + pixman_region32_t rect; + + pixman_region32_init_rect(&rect, x, y, width, height); + pixman_region32_subtract(®ion->region, ®ion->region, &rect); + pixman_region32_fini(&rect); +} + +static const struct wl_region_interface region_interface = { + region_destroy, + region_add, + region_subtract +}; + +static void +compositor_create_region(struct wl_client *client, + struct wl_resource *resource, uint32_t id) +{ + struct weston_region *region; + + region = malloc(sizeof *region); + if (region == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + pixman_region32_init(®ion->region); + + region->resource = + wl_resource_create(client, &wl_region_interface, 1, id); + if (region->resource == NULL) { + free(region); + wl_resource_post_no_memory(resource); + return; + } + wl_resource_set_implementation(region->resource, ®ion_interface, + region, destroy_region); +} + +static const struct wl_compositor_interface compositor_interface = { + compositor_create_surface, + compositor_create_region +}; + +static void +weston_subsurface_commit_from_cache(struct weston_subsurface *sub) +{ + struct weston_surface *surface = sub->surface; + pixman_region32_t opaque; + int surface_width = 0; + int surface_height = 0; + + /* wl_surface.set_buffer_transform */ + surface->buffer_transform = sub->cached.buffer_transform; + + /* wl_surface.set_buffer_scale */ + surface->buffer_scale = sub->cached.buffer_scale; + + /* wl_surface.attach */ + if (sub->cached.buffer_ref.buffer || sub->cached.newly_attached) + weston_surface_attach(surface, sub->cached.buffer_ref.buffer); + weston_buffer_reference(&sub->cached.buffer_ref, NULL); + + if (surface->buffer_ref.buffer) { + surface_width = weston_surface_buffer_width(surface); + surface_height = weston_surface_buffer_height(surface); + } + + if (surface->configure && sub->cached.newly_attached) + surface->configure(surface, sub->cached.sx, sub->cached.sy, + surface_width, surface_height); + sub->cached.sx = 0; + sub->cached.sy = 0; + sub->cached.newly_attached = 0; + + /* wl_surface.damage */ + pixman_region32_union(&surface->damage, &surface->damage, + &sub->cached.damage); + pixman_region32_intersect_rect(&surface->damage, &surface->damage, + 0, 0, + surface->geometry.width, + surface->geometry.height); + empty_region(&sub->cached.damage); + + /* wl_surface.set_opaque_region */ + pixman_region32_init_rect(&opaque, 0, 0, + surface->geometry.width, + surface->geometry.height); + pixman_region32_intersect(&opaque, + &opaque, &sub->cached.opaque); + + if (!pixman_region32_equal(&opaque, &surface->opaque)) { + pixman_region32_copy(&surface->opaque, &opaque); + weston_surface_geometry_dirty(surface); + } + + pixman_region32_fini(&opaque); + + /* wl_surface.set_input_region */ + pixman_region32_fini(&surface->input); + pixman_region32_init_rect(&surface->input, 0, 0, + surface->geometry.width, + surface->geometry.height); + pixman_region32_intersect(&surface->input, + &surface->input, &sub->cached.input); + + /* wl_surface.frame */ + wl_list_insert_list(&surface->frame_callback_list, + &sub->cached.frame_callback_list); + wl_list_init(&sub->cached.frame_callback_list); + + weston_surface_commit_subsurface_order(surface); + + weston_surface_schedule_repaint(surface); + + sub->cached.has_data = 0; +} + +static void +weston_subsurface_commit_to_cache(struct weston_subsurface *sub) +{ + struct weston_surface *surface = sub->surface; + + /* + * If this commit would cause the surface to move by the + * attach(dx, dy) parameters, the old damage region must be + * translated to correspond to the new surface coordinate system + * original_mode. + */ + pixman_region32_translate(&sub->cached.damage, + -surface->pending.sx, -surface->pending.sy); + pixman_region32_union(&sub->cached.damage, &sub->cached.damage, + &surface->pending.damage); + empty_region(&surface->pending.damage); + + if (surface->pending.newly_attached) { + sub->cached.newly_attached = 1; + weston_buffer_reference(&sub->cached.buffer_ref, + surface->pending.buffer); + } + sub->cached.sx += surface->pending.sx; + sub->cached.sy += surface->pending.sy; + surface->pending.sx = 0; + surface->pending.sy = 0; + surface->pending.newly_attached = 0; + + sub->cached.buffer_transform = surface->pending.buffer_transform; + sub->cached.buffer_scale = surface->pending.buffer_scale; + + pixman_region32_copy(&sub->cached.opaque, &surface->pending.opaque); + + pixman_region32_copy(&sub->cached.input, &surface->pending.input); + + wl_list_insert_list(&sub->cached.frame_callback_list, + &surface->pending.frame_callback_list); + wl_list_init(&surface->pending.frame_callback_list); + + sub->cached.has_data = 1; +} + +static int +weston_subsurface_is_synchronized(struct weston_subsurface *sub) +{ + while (sub) { + if (sub->synchronized) + return 1; + + if (!sub->parent) + return 0; + + sub = weston_surface_to_subsurface(sub->parent); + } + + return 0; +} + +static void +weston_subsurface_commit(struct weston_subsurface *sub) +{ + struct weston_surface *surface = sub->surface; + struct weston_subsurface *tmp; + + /* Recursive check for effectively synchronized. */ + if (weston_subsurface_is_synchronized(sub)) { + weston_subsurface_commit_to_cache(sub); + } else { + if (sub->cached.has_data) { + /* flush accumulated state from cache */ + weston_subsurface_commit_to_cache(sub); + weston_subsurface_commit_from_cache(sub); + } else { + weston_surface_commit(surface); + } + + wl_list_for_each(tmp, &surface->subsurface_list, parent_link) { + if (tmp->surface != surface) + weston_subsurface_parent_commit(tmp, 0); + } + } +} + +static void +weston_subsurface_synchronized_commit(struct weston_subsurface *sub) +{ + struct weston_surface *surface = sub->surface; + struct weston_subsurface *tmp; + + /* From now on, commit_from_cache the whole sub-tree, regardless of + * the synchronized mode of each child. This sub-surface or some + * of its ancestors were synchronized, so we are synchronized + * all the way down. + */ + + if (sub->cached.has_data) + weston_subsurface_commit_from_cache(sub); + + wl_list_for_each(tmp, &surface->subsurface_list, parent_link) { + if (tmp->surface != surface) + weston_subsurface_parent_commit(tmp, 1); + } +} + +static void +weston_subsurface_parent_commit(struct weston_subsurface *sub, + int parent_is_synchronized) +{ + if (sub->position.set) { + weston_surface_set_position(sub->surface, + sub->position.x, sub->position.y); + sub->position.set = 0; + } + + if (parent_is_synchronized || sub->synchronized) + weston_subsurface_synchronized_commit(sub); +} + +static void +subsurface_configure(struct weston_surface *surface, int32_t dx, int32_t dy, + int32_t width, int32_t height) +{ + struct weston_compositor *compositor = surface->compositor; + + weston_surface_configure(surface, + surface->geometry.x + dx, + surface->geometry.y + dy, + width, height); + + /* No need to check parent mappedness, because if parent is not + * mapped, parent is not in a visible layer, so this sub-surface + * will not be drawn either. + */ + if (!weston_surface_is_mapped(surface)) { + wl_list_init(&surface->layer_link); + + /* Cannot call weston_surface_update_transform(), + * because that would call it also for the parent surface, + * which might not be mapped yet. That would lead to + * inconsistent state, where the window could never be + * mapped. + * + * Instead just assing any output, to make + * weston_surface_is_mapped() return true, so that when the + * parent surface does get mapped, this one will get + * included, too. See surface_list_add(). + */ + assert(!wl_list_empty(&compositor->output_list)); + surface->output = container_of(compositor->output_list.next, + struct weston_output, link); + } +} + +static struct weston_subsurface * +weston_surface_to_subsurface(struct weston_surface *surface) +{ + if (surface->configure == subsurface_configure) + return surface->configure_private; + + return NULL; +} + +WL_EXPORT struct weston_surface * +weston_surface_get_main_surface(struct weston_surface *surface) +{ + struct weston_subsurface *sub; + + while (surface && (sub = weston_surface_to_subsurface(surface))) + surface = sub->parent; + + return surface; +} + +static void +subsurface_set_position(struct wl_client *client, + struct wl_resource *resource, int32_t x, int32_t y) +{ + struct weston_subsurface *sub = wl_resource_get_user_data(resource); + + if (!sub) + return; + + sub->position.x = x; + sub->position.y = y; + sub->position.set = 1; +} + +static struct weston_subsurface * +subsurface_from_surface(struct weston_surface *surface) +{ + struct weston_subsurface *sub; + + sub = weston_surface_to_subsurface(surface); + if (sub) + return sub; + + wl_list_for_each(sub, &surface->subsurface_list, parent_link) + if (sub->surface == surface) + return sub; + + return NULL; +} + +static struct weston_subsurface * +subsurface_sibling_check(struct weston_subsurface *sub, + struct weston_surface *surface, + const char *request) +{ + struct weston_subsurface *sibling; + + sibling = subsurface_from_surface(surface); + + if (!sibling) { + wl_resource_post_error(sub->resource, + WL_SUBSURFACE_ERROR_BAD_SURFACE, + "%s: wl_surface@%d is not a parent or sibling", + request, wl_resource_get_id(surface->resource)); + return NULL; + } + + if (sibling->parent != sub->parent) { + wl_resource_post_error(sub->resource, + WL_SUBSURFACE_ERROR_BAD_SURFACE, + "%s: wl_surface@%d has a different parent", + request, wl_resource_get_id(surface->resource)); + return NULL; + } + + return sibling; +} + +static void +subsurface_place_above(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *sibling_resource) +{ + struct weston_subsurface *sub = wl_resource_get_user_data(resource); + struct weston_surface *surface = + wl_resource_get_user_data(sibling_resource); + struct weston_subsurface *sibling; + + if (!sub) + return; + + sibling = subsurface_sibling_check(sub, surface, "place_above"); + if (!sibling) + return; + + wl_list_remove(&sub->parent_link_pending); + wl_list_insert(sibling->parent_link_pending.prev, + &sub->parent_link_pending); +} + +static void +subsurface_place_below(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *sibling_resource) +{ + struct weston_subsurface *sub = wl_resource_get_user_data(resource); + struct weston_surface *surface = + wl_resource_get_user_data(sibling_resource); + struct weston_subsurface *sibling; + + if (!sub) + return; + + sibling = subsurface_sibling_check(sub, surface, "place_below"); + if (!sibling) + return; + + wl_list_remove(&sub->parent_link_pending); + wl_list_insert(&sibling->parent_link_pending, + &sub->parent_link_pending); +} + +static void +subsurface_set_sync(struct wl_client *client, struct wl_resource *resource) +{ + struct weston_subsurface *sub = wl_resource_get_user_data(resource); + + if (sub) + sub->synchronized = 1; +} + +static void +subsurface_set_desync(struct wl_client *client, struct wl_resource *resource) +{ + struct weston_subsurface *sub = wl_resource_get_user_data(resource); + + if (sub && sub->synchronized) { + sub->synchronized = 0; + + /* If sub became effectively desynchronized, flush. */ + if (!weston_subsurface_is_synchronized(sub)) + weston_subsurface_synchronized_commit(sub); + } +} + +static void +weston_subsurface_cache_init(struct weston_subsurface *sub) +{ + pixman_region32_init(&sub->cached.damage); + pixman_region32_init(&sub->cached.opaque); + pixman_region32_init(&sub->cached.input); + wl_list_init(&sub->cached.frame_callback_list); + sub->cached.buffer_ref.buffer = NULL; +} + +static void +weston_subsurface_cache_fini(struct weston_subsurface *sub) +{ + struct weston_frame_callback *cb, *tmp; + + wl_list_for_each_safe(cb, tmp, &sub->cached.frame_callback_list, link) + wl_resource_destroy(cb->resource); + + weston_buffer_reference(&sub->cached.buffer_ref, NULL); + pixman_region32_fini(&sub->cached.damage); + pixman_region32_fini(&sub->cached.opaque); + pixman_region32_fini(&sub->cached.input); +} + +static void +weston_subsurface_unlink_parent(struct weston_subsurface *sub) +{ + wl_list_remove(&sub->parent_link); + wl_list_remove(&sub->parent_link_pending); + wl_list_remove(&sub->parent_destroy_listener.link); + sub->parent = NULL; +} + +static void +weston_subsurface_destroy(struct weston_subsurface *sub); + +static void +subsurface_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct weston_subsurface *sub = + container_of(listener, struct weston_subsurface, + surface_destroy_listener); + assert(data == &sub->surface->resource); + + /* The protocol object (wl_resource) is left inert. */ + if (sub->resource) + wl_resource_set_user_data(sub->resource, NULL); + + weston_subsurface_destroy(sub); +} + +static void +subsurface_handle_parent_destroy(struct wl_listener *listener, void *data) +{ + struct weston_subsurface *sub = + container_of(listener, struct weston_subsurface, + parent_destroy_listener); + assert(data == &sub->parent->resource); + assert(sub->surface != sub->parent); + + if (weston_surface_is_mapped(sub->surface)) + weston_surface_unmap(sub->surface); + + weston_subsurface_unlink_parent(sub); +} + +static void +subsurface_resource_destroy(struct wl_resource *resource) +{ + struct weston_subsurface *sub = wl_resource_get_user_data(resource); + + if (sub) + weston_subsurface_destroy(sub); +} + +static void +subsurface_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +weston_subsurface_link_parent(struct weston_subsurface *sub, + struct weston_surface *parent) +{ + sub->parent = parent; + sub->parent_destroy_listener.notify = subsurface_handle_parent_destroy; + wl_signal_add(&parent->destroy_signal, + &sub->parent_destroy_listener); + + wl_list_insert(&parent->subsurface_list, &sub->parent_link); + wl_list_insert(&parent->subsurface_list_pending, + &sub->parent_link_pending); +} + +static void +weston_subsurface_link_surface(struct weston_subsurface *sub, + struct weston_surface *surface) +{ + sub->surface = surface; + sub->surface_destroy_listener.notify = + subsurface_handle_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &sub->surface_destroy_listener); +} + +static void +weston_subsurface_destroy(struct weston_subsurface *sub) +{ + assert(sub->surface); + + if (sub->resource) { + assert(weston_surface_to_subsurface(sub->surface) == sub); + assert(sub->parent_destroy_listener.notify == + subsurface_handle_parent_destroy); + + weston_surface_set_transform_parent(sub->surface, NULL); + if (sub->parent) + weston_subsurface_unlink_parent(sub); + + weston_subsurface_cache_fini(sub); + + sub->surface->configure = NULL; + sub->surface->configure_private = NULL; + } else { + /* the dummy weston_subsurface for the parent itself */ + assert(sub->parent_destroy_listener.notify == NULL); + wl_list_remove(&sub->parent_link); + wl_list_remove(&sub->parent_link_pending); + } + + wl_list_remove(&sub->surface_destroy_listener.link); + free(sub); +} + +static const struct wl_subsurface_interface subsurface_implementation = { + subsurface_destroy, + subsurface_set_position, + subsurface_place_above, + subsurface_place_below, + subsurface_set_sync, + subsurface_set_desync +}; + +static struct weston_subsurface * +weston_subsurface_create(uint32_t id, struct weston_surface *surface, + struct weston_surface *parent) +{ + struct weston_subsurface *sub; + struct wl_client *client = wl_resource_get_client(surface->resource); + + sub = calloc(1, sizeof *sub); + if (!sub) + return NULL; + + sub->resource = + wl_resource_create(client, &wl_subsurface_interface, 1, id); + if (!sub->resource) { + free(sub); + return NULL; + } + + wl_resource_set_implementation(sub->resource, + &subsurface_implementation, + sub, subsurface_resource_destroy); + weston_subsurface_link_surface(sub, surface); + weston_subsurface_link_parent(sub, parent); + weston_subsurface_cache_init(sub); + sub->synchronized = 1; + weston_surface_set_transform_parent(surface, parent); + + return sub; +} + +/* Create a dummy subsurface for having the parent itself in its + * sub-surface lists. Makes stacking order manipulation easy. + */ +static struct weston_subsurface * +weston_subsurface_create_for_parent(struct weston_surface *parent) +{ + struct weston_subsurface *sub; + + sub = calloc(1, sizeof *sub); + if (!sub) + return NULL; + + weston_subsurface_link_surface(sub, parent); + sub->parent = parent; + wl_list_insert(&parent->subsurface_list, &sub->parent_link); + wl_list_insert(&parent->subsurface_list_pending, + &sub->parent_link_pending); + + return sub; +} + +static void +subcompositor_get_subsurface(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource, + struct wl_resource *parent_resource) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct weston_surface *parent = + wl_resource_get_user_data(parent_resource); + struct weston_subsurface *sub; + static const char where[] = "get_subsurface: wl_subsurface@"; + + if (surface == parent) { + wl_resource_post_error(resource, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "%s%d: wl_surface@%d cannot be its own parent", + where, id, wl_resource_get_id(surface_resource)); + return; + } + + if (weston_surface_to_subsurface(surface)) { + wl_resource_post_error(resource, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "%s%d: wl_surface@%d is already a sub-surface", + where, id, wl_resource_get_id(surface_resource)); + return; + } + + if (surface->configure) { + wl_resource_post_error(resource, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "%s%d: wl_surface@%d already has a role", + where, id, wl_resource_get_id(surface_resource)); + return; + } + + if (weston_surface_get_main_surface(parent) == surface) { + wl_resource_post_error(resource, + WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "%s%d: wl_surface@%d is an ancestor of parent", + where, id, wl_resource_get_id(surface_resource)); + return; + } + + /* make sure the parent is in its own list */ + if (wl_list_empty(&parent->subsurface_list)) { + if (!weston_subsurface_create_for_parent(parent)) { + wl_resource_post_no_memory(resource); + return; + } + } + + sub = weston_subsurface_create(id, surface, parent); + if (!sub) { + wl_resource_post_no_memory(resource); + return; + } + + surface->configure = subsurface_configure; + surface->configure_private = sub; +} + +static void +subcompositor_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_subcompositor_interface subcompositor_interface = { + subcompositor_destroy, + subcompositor_get_subsurface +}; + +static void +bind_subcompositor(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct weston_compositor *compositor = data; + struct wl_resource *resource; + + resource = + wl_resource_create(client, &wl_subcompositor_interface, 1, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &subcompositor_interface, + compositor, NULL); +} + +static void +weston_compositor_dpms(struct weston_compositor *compositor, + enum dpms_enum state) +{ + struct weston_output *output; + + wl_list_for_each(output, &compositor->output_list, link) + if (output->set_dpms) + output->set_dpms(output, state); +} + +WL_EXPORT void +weston_compositor_wake(struct weston_compositor *compositor) +{ + uint32_t old_state = compositor->state; + + /* The state needs to be changed before emitting the wake + * signal because that may try to schedule a repaint which + * will not work if the compositor is still sleeping */ + compositor->state = WESTON_COMPOSITOR_ACTIVE; + + switch (old_state) { + case WESTON_COMPOSITOR_SLEEPING: + weston_compositor_dpms(compositor, WESTON_DPMS_ON); + /* fall through */ + case WESTON_COMPOSITOR_IDLE: + case WESTON_COMPOSITOR_OFFSCREEN: + wl_signal_emit(&compositor->wake_signal, compositor); + /* fall through */ + default: + wl_event_source_timer_update(compositor->idle_source, + compositor->idle_time * 1000); + } +} + +WL_EXPORT void +weston_compositor_offscreen(struct weston_compositor *compositor) +{ + switch (compositor->state) { + case WESTON_COMPOSITOR_OFFSCREEN: + return; + case WESTON_COMPOSITOR_SLEEPING: + weston_compositor_dpms(compositor, WESTON_DPMS_ON); + /* fall through */ + default: + compositor->state = WESTON_COMPOSITOR_OFFSCREEN; + wl_event_source_timer_update(compositor->idle_source, 0); + } +} + +WL_EXPORT void +weston_compositor_sleep(struct weston_compositor *compositor) +{ + if (compositor->state == WESTON_COMPOSITOR_SLEEPING) + return; + + wl_event_source_timer_update(compositor->idle_source, 0); + compositor->state = WESTON_COMPOSITOR_SLEEPING; + weston_compositor_dpms(compositor, WESTON_DPMS_OFF); +} + +static int +idle_handler(void *data) +{ + struct weston_compositor *compositor = data; + + if (compositor->idle_inhibit) + return 1; + + compositor->state = WESTON_COMPOSITOR_IDLE; + wl_signal_emit(&compositor->idle_signal, compositor); + + return 1; +} + +WL_EXPORT void +weston_plane_init(struct weston_plane *plane, + struct weston_compositor *ec, + int32_t x, int32_t y) +{ + pixman_region32_init(&plane->damage); + pixman_region32_init(&plane->clip); + plane->x = x; + plane->y = y; + plane->compositor = ec; + + /* Init the link so that the call to wl_list_remove() when releasing + * the plane without ever stacking doesn't lead to a crash */ + wl_list_init(&plane->link); +} + +WL_EXPORT void +weston_plane_release(struct weston_plane *plane) +{ + struct weston_surface *surface; + + pixman_region32_fini(&plane->damage); + pixman_region32_fini(&plane->clip); + + wl_list_for_each(surface, &plane->compositor->surface_list, link) { + if (surface->plane == plane) + surface->plane = NULL; + } + + wl_list_remove(&plane->link); +} + +WL_EXPORT void +weston_compositor_stack_plane(struct weston_compositor *ec, + struct weston_plane *plane, + struct weston_plane *above) +{ + if (above) + wl_list_insert(above->link.prev, &plane->link); + else + wl_list_insert(&ec->plane_list, &plane->link); +} + +static void unbind_resource(struct wl_resource *resource) +{ + wl_list_remove(wl_resource_get_link(resource)); +} + +static void +bind_output(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct weston_output *output = data; + struct weston_mode *mode; + struct wl_resource *resource; + + resource = wl_resource_create(client, &wl_output_interface, + MIN(version, 2), id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_list_insert(&output->resource_list, wl_resource_get_link(resource)); + wl_resource_set_implementation(resource, NULL, data, unbind_resource); + + wl_output_send_geometry(resource, + output->x, + output->y, + output->mm_width, + output->mm_height, + output->subpixel, + output->make, output->model, + output->transform); + if (version >= 2) + wl_output_send_scale(resource, + output->current_scale); + + wl_list_for_each (mode, &output->mode_list, link) { + wl_output_send_mode(resource, + mode->flags, + mode->width, + mode->height, + mode->refresh); + } + + if (version >= 2) + wl_output_send_done(resource); +} + +WL_EXPORT void +weston_output_destroy(struct weston_output *output) +{ + wl_signal_emit(&output->destroy_signal, output); + + free(output->name); + pixman_region32_fini(&output->region); + pixman_region32_fini(&output->previous_damage); + output->compositor->output_id_pool &= ~(1 << output->id); + + wl_global_destroy(output->global); +} + +static void +weston_output_compute_transform(struct weston_output *output) +{ + struct weston_matrix transform; + int flip; + + weston_matrix_init(&transform); + transform.type = WESTON_MATRIX_TRANSFORM_ROTATE; + + switch(output->transform) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + transform.type |= WESTON_MATRIX_TRANSFORM_OTHER; + flip = -1; + break; + default: + flip = 1; + break; + } + + switch(output->transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_FLIPPED: + transform.d[0] = flip; + transform.d[1] = 0; + transform.d[4] = 0; + transform.d[5] = 1; + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + transform.d[0] = 0; + transform.d[1] = -flip; + transform.d[4] = 1; + transform.d[5] = 0; + break; + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + transform.d[0] = -flip; + transform.d[1] = 0; + transform.d[4] = 0; + transform.d[5] = -1; + break; + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + transform.d[0] = 0; + transform.d[1] = flip; + transform.d[4] = -1; + transform.d[5] = 0; + break; + default: + break; + } + + weston_matrix_multiply(&output->matrix, &transform); +} + +WL_EXPORT void +weston_output_update_matrix(struct weston_output *output) +{ + float magnification; + struct weston_matrix camera; + struct weston_matrix modelview; + + weston_matrix_init(&output->matrix); + weston_matrix_translate(&output->matrix, + -(output->x + (output->border.right + output->width - output->border.left) / 2.0), + -(output->y + (output->border.bottom + output->height - output->border.top) / 2.0), 0); + + weston_matrix_scale(&output->matrix, + 2.0 / (output->width + output->border.left + output->border.right), + -2.0 / (output->height + output->border.top + output->border.bottom), 1); + + weston_output_compute_transform(output); + + if (output->zoom.active) { + magnification = 1 / (1 - output->zoom.spring_z.current); + weston_matrix_init(&camera); + weston_matrix_init(&modelview); + weston_output_update_zoom(output, output->zoom.type); + weston_matrix_translate(&camera, output->zoom.trans_x, + -output->zoom.trans_y, 0); + weston_matrix_invert(&modelview, &camera); + weston_matrix_scale(&modelview, magnification, magnification, 1.0); + weston_matrix_multiply(&output->matrix, &modelview); + } + + output->dirty = 0; +} + +static void +weston_output_transform_scale_init(struct weston_output *output, uint32_t transform, uint32_t scale) +{ + output->transform = transform; + + switch (transform) { + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + /* Swap width and height */ + output->width = output->current_mode->height; + output->height = output->current_mode->width; + break; + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + output->width = output->current_mode->width; + output->height = output->current_mode->height; + break; + default: + break; + } + + output->native_scale = output->current_scale = scale; + output->width /= scale; + output->height /= scale; +} + +WL_EXPORT void +weston_output_move(struct weston_output *output, int x, int y) +{ + output->x = x; + output->y = y; + + pixman_region32_init(&output->previous_damage); + pixman_region32_init_rect(&output->region, x, y, + output->width, + output->height); +} + +WL_EXPORT void +weston_output_init(struct weston_output *output, struct weston_compositor *c, + int x, int y, int mm_width, int mm_height, uint32_t transform, + int32_t scale) +{ + output->compositor = c; + output->x = x; + output->y = y; + output->border.top = 0; + output->border.bottom = 0; + output->border.left = 0; + output->border.right = 0; + output->mm_width = mm_width; + output->mm_height = mm_height; + output->dirty = 1; + output->original_scale = scale; + + weston_output_transform_scale_init(output, transform, scale); + weston_output_init_zoom(output); + + weston_output_move(output, x, y); + weston_output_damage(output); + + wl_signal_init(&output->frame_signal); + wl_signal_init(&output->destroy_signal); + wl_list_init(&output->animation_list); + wl_list_init(&output->resource_list); + + output->id = ffs(~output->compositor->output_id_pool) - 1; + output->compositor->output_id_pool |= 1 << output->id; + + output->global = + wl_global_create(c->wl_display, &wl_output_interface, 2, + output, bind_output); + wl_signal_emit(&c->output_created_signal, output); +} + +WL_EXPORT void +weston_output_transform_coordinate(struct weston_output *output, + int device_x, int device_y, + wl_fixed_t *x, wl_fixed_t *y) +{ + wl_fixed_t tx, ty; + wl_fixed_t width, height; + + width = wl_fixed_from_int(output->width * output->current_scale - 1); + height = wl_fixed_from_int(output->height * output->current_scale - 1); + + switch(output->transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + default: + tx = wl_fixed_from_int(device_x); + ty = wl_fixed_from_int(device_y); + break; + case WL_OUTPUT_TRANSFORM_90: + tx = wl_fixed_from_int(device_y); + ty = height - wl_fixed_from_int(device_x); + break; + case WL_OUTPUT_TRANSFORM_180: + tx = width - wl_fixed_from_int(device_x); + ty = height - wl_fixed_from_int(device_y); + break; + case WL_OUTPUT_TRANSFORM_270: + tx = width - wl_fixed_from_int(device_y); + ty = wl_fixed_from_int(device_x); + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + tx = width - wl_fixed_from_int(device_x); + ty = wl_fixed_from_int(device_y); + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + tx = width - wl_fixed_from_int(device_y); + ty = height - wl_fixed_from_int(device_x); + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + tx = wl_fixed_from_int(device_x); + ty = height - wl_fixed_from_int(device_y); + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + tx = wl_fixed_from_int(device_y); + ty = wl_fixed_from_int(device_x); + break; + } + + *x = tx / output->current_scale + wl_fixed_from_int(output->x); + *y = ty / output->current_scale + wl_fixed_from_int(output->y); +} + +static void +compositor_bind(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct weston_compositor *compositor = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &wl_compositor_interface, + MIN(version, 3), id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &compositor_interface, + compositor, NULL); +} + +static void +log_uname(void) +{ + struct utsname usys; + + uname(&usys); + + weston_log("OS: %s, %s, %s, %s\n", usys.sysname, usys.release, + usys.version, usys.machine); +} + +WL_EXPORT int +weston_environment_get_fd(const char *env) +{ + char *e, *end; + int fd, flags; + + e = getenv(env); + if (!e) + return -1; + fd = strtol(e, &end, 0); + if (*end != '\0') + return -1; + + flags = fcntl(fd, F_GETFD); + if (flags == -1) + return -1; + + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + unsetenv(env); + + return fd; +} + +WL_EXPORT int +weston_compositor_init(struct weston_compositor *ec, + struct wl_display *display, + int *argc, char *argv[], + struct weston_config *config) +{ + struct wl_event_loop *loop; + struct xkb_rule_names xkb_names; + struct weston_config_section *s; + + ec->config = config; + ec->wl_display = display; + wl_signal_init(&ec->destroy_signal); + wl_signal_init(&ec->activate_signal); + wl_signal_init(&ec->transform_signal); + wl_signal_init(&ec->kill_signal); + wl_signal_init(&ec->idle_signal); + wl_signal_init(&ec->wake_signal); + wl_signal_init(&ec->show_input_panel_signal); + wl_signal_init(&ec->hide_input_panel_signal); + wl_signal_init(&ec->update_input_panel_signal); + wl_signal_init(&ec->seat_created_signal); + wl_signal_init(&ec->output_created_signal); + wl_signal_init(&ec->session_signal); + ec->session_active = 1; + + ec->output_id_pool = 0; + + if (!wl_global_create(display, &wl_compositor_interface, 3, + ec, compositor_bind)) + return -1; + + if (!wl_global_create(display, &wl_subcompositor_interface, 1, + ec, bind_subcompositor)) + return -1; + + wl_list_init(&ec->surface_list); + wl_list_init(&ec->plane_list); + wl_list_init(&ec->layer_list); + wl_list_init(&ec->seat_list); + wl_list_init(&ec->output_list); + wl_list_init(&ec->key_binding_list); + wl_list_init(&ec->button_binding_list); + wl_list_init(&ec->touch_binding_list); + wl_list_init(&ec->axis_binding_list); + wl_list_init(&ec->debug_binding_list); + + weston_plane_init(&ec->primary_plane, ec, 0, 0); + weston_compositor_stack_plane(ec, &ec->primary_plane, NULL); + + s = weston_config_get_section(ec->config, "keyboard", NULL, NULL); + weston_config_section_get_string(s, "keymap_rules", + (char **) &xkb_names.rules, NULL); + weston_config_section_get_string(s, "keymap_model", + (char **) &xkb_names.model, NULL); + weston_config_section_get_string(s, "keymap_layout", + (char **) &xkb_names.layout, NULL); + weston_config_section_get_string(s, "keymap_variant", + (char **) &xkb_names.variant, NULL); + weston_config_section_get_string(s, "keymap_options", + (char **) &xkb_names.options, NULL); + + if (weston_compositor_xkb_init(ec, &xkb_names) < 0) + return -1; + + ec->ping_handler = NULL; + + screenshooter_create(ec); + text_cursor_position_notifier_create(ec); + text_backend_init(ec); + + wl_data_device_manager_init(ec->wl_display); + + wl_display_init_shm(display); + + loop = wl_display_get_event_loop(ec->wl_display); + ec->idle_source = wl_event_loop_add_timer(loop, idle_handler, ec); + wl_event_source_timer_update(ec->idle_source, ec->idle_time * 1000); + + ec->input_loop = wl_event_loop_create(); + + weston_layer_init(&ec->fade_layer, &ec->layer_list); + weston_layer_init(&ec->cursor_layer, &ec->fade_layer.link); + + weston_compositor_schedule_repaint(ec); + + return 0; +} + +WL_EXPORT void +weston_compositor_shutdown(struct weston_compositor *ec) +{ + struct weston_output *output, *next; + + wl_event_source_remove(ec->idle_source); + if (ec->input_loop_source) + wl_event_source_remove(ec->input_loop_source); + + /* Destroy all outputs associated with this compositor */ + wl_list_for_each_safe(output, next, &ec->output_list, link) + output->destroy(output); + + weston_binding_list_destroy_all(&ec->key_binding_list); + weston_binding_list_destroy_all(&ec->button_binding_list); + weston_binding_list_destroy_all(&ec->touch_binding_list); + weston_binding_list_destroy_all(&ec->axis_binding_list); + weston_binding_list_destroy_all(&ec->debug_binding_list); + + weston_plane_release(&ec->primary_plane); + + wl_event_loop_destroy(ec->input_loop); + + weston_config_destroy(ec->config); +} + +WL_EXPORT void +weston_version(int *major, int *minor, int *micro) +{ + *major = WESTON_VERSION_MAJOR; + *minor = WESTON_VERSION_MINOR; + *micro = WESTON_VERSION_MICRO; +} + +static const struct { + uint32_t bit; /* enum weston_capability */ + const char *desc; +} capability_strings[] = { + { WESTON_CAP_ROTATION_ANY, "arbitrary surface rotation:" }, + { WESTON_CAP_CAPTURE_YFLIP, "screen capture uses y-flip:" }, +}; + +static void +weston_compositor_log_capabilities(struct weston_compositor *compositor) +{ + unsigned i; + int yes; + + weston_log("Compositor capabilities:\n"); + for (i = 0; i < ARRAY_LENGTH(capability_strings); i++) { + yes = compositor->capabilities & capability_strings[i].bit; + weston_log_continue(STAMP_SPACE "%s %s\n", + capability_strings[i].desc, + yes ? "yes" : "no"); + } +} + +static int on_term_signal(int signal_number, void *data) +{ + struct wl_display *display = data; + + weston_log("caught signal %d\n", signal_number); + wl_display_terminate(display); + + return 1; +} + +#ifdef HAVE_LIBUNWIND + +static void +print_backtrace(void) +{ + unw_cursor_t cursor; + unw_context_t context; + unw_word_t off; + unw_proc_info_t pip; + int ret, i = 0; + char procname[256]; + const char *filename; + Dl_info dlinfo; + + pip.unwind_info = NULL; + ret = unw_getcontext(&context); + if (ret) { + weston_log("unw_getcontext: %d\n", ret); + return; + } + + ret = unw_init_local(&cursor, &context); + if (ret) { + weston_log("unw_init_local: %d\n", ret); + return; + } + + ret = unw_step(&cursor); + while (ret > 0) { + ret = unw_get_proc_info(&cursor, &pip); + if (ret) { + weston_log("unw_get_proc_info: %d\n", ret); + break; + } + + ret = unw_get_proc_name(&cursor, procname, 256, &off); + if (ret && ret != -UNW_ENOMEM) { + if (ret != -UNW_EUNSPEC) + weston_log("unw_get_proc_name: %d\n", ret); + procname[0] = '?'; + procname[1] = 0; + } + + if (dladdr((void *)(pip.start_ip + off), &dlinfo) && dlinfo.dli_fname && + *dlinfo.dli_fname) + filename = dlinfo.dli_fname; + else + filename = "?"; + + weston_log("%u: %s (%s%s+0x%x) [%p]\n", i++, filename, procname, + ret == -UNW_ENOMEM ? "..." : "", (int)off, (void *)(pip.start_ip + off)); + + ret = unw_step(&cursor); + if (ret < 0) + weston_log("unw_step: %d\n", ret); + } +} + +#else + +static void +print_backtrace(void) +{ + void *buffer[32]; + int i, count; + Dl_info info; + + count = backtrace(buffer, ARRAY_LENGTH(buffer)); + for (i = 0; i < count; i++) { + dladdr(buffer[i], &info); + weston_log(" [%016lx] %s (%s)\n", + (long) buffer[i], + info.dli_sname ? info.dli_sname : "--", + info.dli_fname); + } +} + +#endif + +static void +on_caught_signal(int s, siginfo_t *siginfo, void *context) +{ + /* This signal handler will do a best-effort backtrace, and + * then call the backend restore function, which will switch + * back to the vt we launched from or ungrab X etc and then + * raise SIGTRAP. If we run weston under gdb from X or a + * different vt, and tell gdb "handle *s* nostop", this + * will allow weston to switch back to gdb on crash and then + * gdb will catch the crash with SIGTRAP.*/ + + weston_log("caught signal: %d\n", s); + + print_backtrace(); + + segv_compositor->restore(segv_compositor); + + raise(SIGTRAP); +} + +static void * +load_module(const char *name, const char *entrypoint) +{ + char path[PATH_MAX]; + void *module, *init; + + if (name[0] != '/') + snprintf(path, sizeof path, "%s/%s", MODULEDIR, name); + else + snprintf(path, sizeof path, "%s", name); + + module = dlopen(path, RTLD_NOW | RTLD_NOLOAD); + if (module) { + weston_log("Module '%s' already loaded\n", path); + dlclose(module); + return NULL; + } + + weston_log("Loading module '%s'\n", path); + module = dlopen(path, RTLD_NOW); + if (!module) { + weston_log("Failed to load module: %s\n", dlerror()); + return NULL; + } + + init = dlsym(module, entrypoint); + if (!init) { + weston_log("Failed to lookup init function: %s\n", dlerror()); + dlclose(module); + return NULL; + } + + return init; +} + +static int +load_modules(struct weston_compositor *ec, const char *modules, + int *argc, char *argv[]) +{ + const char *p, *end; + char buffer[256]; + int (*module_init)(struct weston_compositor *ec, + int *argc, char *argv[]); + + if (modules == NULL) + return 0; + + p = modules; + while (*p) { + end = strchrnul(p, ','); + snprintf(buffer, sizeof buffer, "%.*s", (int) (end - p), p); + module_init = load_module(buffer, "module_init"); + if (module_init) + module_init(ec, argc, argv); + p = end; + while (*p == ',') + p++; + + } + + return 0; +} + +static const char xdg_error_message[] = + "fatal: environment variable XDG_RUNTIME_DIR is not set.\n"; + +static const char xdg_wrong_message[] = + "fatal: environment variable XDG_RUNTIME_DIR\n" + "is set to \"%s\", which is not a directory.\n"; + +static const char xdg_wrong_mode_message[] = + "warning: XDG_RUNTIME_DIR \"%s\" is not configured\n" + "correctly. Unix access mode must be 0700 but is %o,\n" + "and XDG_RUNTIME_DIR must be owned by the user, but is\n" + "owned by UID %d.\n"; + +static const char xdg_detail_message[] = + "Refer to your distribution on how to get it, or\n" + "http://www.freedesktop.org/wiki/Specifications/basedir-spec\n" + "on how to implement it.\n"; + +static void +verify_xdg_runtime_dir(void) +{ + char *dir = getenv("XDG_RUNTIME_DIR"); + struct stat s; + + if (!dir) { + weston_log(xdg_error_message); + weston_log_continue(xdg_detail_message); + exit(EXIT_FAILURE); + } + + if (stat(dir, &s) || !S_ISDIR(s.st_mode)) { + weston_log(xdg_wrong_message, dir); + weston_log_continue(xdg_detail_message); + exit(EXIT_FAILURE); + } + + if ((s.st_mode & 0777) != 0700 || s.st_uid != getuid()) { + weston_log(xdg_wrong_mode_message, + dir, s.st_mode & 0777, s.st_uid); + weston_log_continue(xdg_detail_message); + } +} + +static int +usage(int error_code) +{ + fprintf(stderr, + "Usage: weston [OPTIONS]\n\n" + "This is weston version " VERSION ", the Wayland reference compositor.\n" + "Weston supports multiple backends, and depending on which backend is in use\n" + "different options will be accepted.\n\n" + + + "Core options:\n\n" + " --version\t\tPrint weston version\n" + " -B, --backend=MODULE\tBackend module, one of drm-backend.so,\n" + "\t\t\t\tfbdev-backend.so, x11-backend.so or\n" + "\t\t\t\twayland-backend.so\n" + " --shell=MODULE\tShell module, defaults to desktop-shell.so\n" + " -S, --socket=NAME\tName of socket to listen on\n" + " -i, --idle-time=SECS\tIdle time in seconds\n" + " --modules\t\tLoad the comma-separated list of modules\n" + " --log==FILE\t\tLog to the given file\n" + " -h, --help\t\tThis help message\n\n"); + + fprintf(stderr, + "Options for drm-backend.so:\n\n" + " --connector=ID\tBring up only this connector\n" + " --seat=SEAT\t\tThe seat that weston should run on\n" + " --tty=TTY\t\tThe tty to use\n" + " --use-pixman\t\tUse the pixman (CPU) renderer\n" + " --current-mode\tPrefer current KMS mode over EDID preferred mode\n\n"); + + fprintf(stderr, + "Options for fbdev-backend.so:\n\n" + " --tty=TTY\t\tThe tty to use\n" + " --device=DEVICE\tThe framebuffer device to use\n\n"); + + fprintf(stderr, + "Options for x11-backend.so:\n\n" + " --width=WIDTH\t\tWidth of X window\n" + " --height=HEIGHT\tHeight of X window\n" + " --fullscreen\t\tRun in fullscreen mode\n" + " --use-pixman\t\tUse the pixman (CPU) renderer\n" + " --output-count=COUNT\tCreate multiple outputs\n" + " --no-input\t\tDont create input devices\n\n"); + + fprintf(stderr, + "Options for wayland-backend.so:\n\n" + " --width=WIDTH\t\tWidth of Wayland surface\n" + " --height=HEIGHT\tHeight of Wayland surface\n" + " --display=DISPLAY\tWayland display to connect to\n\n"); + +#if defined(BUILD_RPI_COMPOSITOR) && defined(HAVE_BCM_HOST) + fprintf(stderr, + "Options for rpi-backend.so:\n\n" + " --tty=TTY\t\tThe tty to use\n" + " --single-buffer\tUse single-buffered Dispmanx elements.\n" + " --transform=TR\tThe output transformation, TR is one of:\n" + "\tnormal 90 180 270 flipped flipped-90 flipped-180 flipped-270\n" + "\n"); +#endif + +#if defined(BUILD_RDP_COMPOSITOR) + fprintf(stderr, + "Options for rdp-backend.so:\n\n" + " --width=WIDTH\t\tWidth of desktop\n" + " --height=HEIGHT\tHeight of desktop\n" + " --extra-modes=MODES\t\n" + " --env-socket=SOCKET\tUse that socket as peer connection\n" + " --address=ADDR\tThe address to bind\n" + " --port=PORT\tThe port to listen on\n" + " --rdp4-key=FILE\tThe file containing the key for RDP4 encryption\n" + " --rdp-tls-cert=FILE\tThe file containing the certificate for TLS encryption\n" + " --rdp-tls-key=FILE\tThe file containing the private key for TLS encryption\n" + "\n"); +#endif + + exit(error_code); +} + +static void +catch_signals(void) +{ + struct sigaction action; + + action.sa_flags = SA_SIGINFO | SA_RESETHAND; + action.sa_sigaction = on_caught_signal; + sigemptyset(&action.sa_mask); + sigaction(SIGSEGV, &action, NULL); + sigaction(SIGABRT, &action, NULL); +} + +int main(int argc, char *argv[]) +{ + int ret = EXIT_SUCCESS; + struct wl_display *display; + struct weston_compositor *ec; + struct wl_event_source *signals[4]; + struct wl_event_loop *loop; + struct weston_compositor + *(*backend_init)(struct wl_display *display, + int *argc, char *argv[], + struct weston_config *config); + int i; + char *backend = NULL; + char *shell = NULL; + char *modules, *option_modules = NULL; + char *log = NULL; + int32_t idle_time = 300; + int32_t help = 0; + char *socket_name = "wayland-0"; + int32_t version = 0; + struct weston_config *config; + struct weston_config_section *section; + + const struct weston_option core_options[] = { + { WESTON_OPTION_STRING, "backend", 'B', &backend }, + { WESTON_OPTION_STRING, "shell", 0, &shell }, + { WESTON_OPTION_STRING, "socket", 'S', &socket_name }, + { WESTON_OPTION_INTEGER, "idle-time", 'i', &idle_time }, + { WESTON_OPTION_STRING, "modules", 0, &option_modules }, + { WESTON_OPTION_STRING, "log", 0, &log }, + { WESTON_OPTION_BOOLEAN, "help", 'h', &help }, + { WESTON_OPTION_BOOLEAN, "version", 0, &version }, + }; + + parse_options(core_options, ARRAY_LENGTH(core_options), &argc, argv); + + if (help) + usage(EXIT_SUCCESS); + + if (version) { + printf(PACKAGE_STRING "\n"); + return EXIT_SUCCESS; + } + + weston_log_file_open(log); + + weston_log("%s\n" + STAMP_SPACE "%s\n" + STAMP_SPACE "Bug reports to: %s\n" + STAMP_SPACE "Build: %s\n", + PACKAGE_STRING, PACKAGE_URL, PACKAGE_BUGREPORT, + BUILD_ID); + log_uname(); + + verify_xdg_runtime_dir(); + + display = wl_display_create(); + + loop = wl_display_get_event_loop(display); + signals[0] = wl_event_loop_add_signal(loop, SIGTERM, on_term_signal, + display); + signals[1] = wl_event_loop_add_signal(loop, SIGINT, on_term_signal, + display); + signals[2] = wl_event_loop_add_signal(loop, SIGQUIT, on_term_signal, + display); + + wl_list_init(&child_process_list); + signals[3] = wl_event_loop_add_signal(loop, SIGCHLD, sigchld_handler, + NULL); + + if (!backend) { + if (getenv("WAYLAND_DISPLAY")) + backend = "wayland-backend.so"; + else if (getenv("DISPLAY")) + backend = "x11-backend.so"; + else + backend = WESTON_NATIVE_BACKEND; + } + + config = weston_config_parse("weston.ini"); + if (config != NULL) { + weston_log("Using config file '%s'\n", + weston_config_get_full_path(config)); + } else { + weston_log("Starting with no config file.\n"); + } + section = weston_config_get_section(config, "core", NULL, NULL); + weston_config_section_get_string(section, "modules", &modules, ""); + + backend_init = load_module(backend, "backend_init"); + if (!backend_init) + exit(EXIT_FAILURE); + + ec = backend_init(display, &argc, argv, config); + if (ec == NULL) { + weston_log("fatal: failed to create compositor\n"); + exit(EXIT_FAILURE); + } + + catch_signals(); + segv_compositor = ec; + + ec->idle_time = idle_time; + + setenv("WAYLAND_DISPLAY", socket_name, 1); + + if (!shell) + weston_config_section_get_string(section, "shell", &shell, + "desktop-shell.so"); + if (load_modules(ec, shell, &argc, argv) < 0) + goto out; + + if (load_modules(ec, modules, &argc, argv) < 0) + goto out; + if (load_modules(ec, option_modules, &argc, argv) < 0) + goto out; + + for (i = 1; i < argc; i++) + weston_log("fatal: unhandled option: %s\n", argv[i]); + if (argc > 1) { + ret = EXIT_FAILURE; + goto out; + } + + weston_compositor_log_capabilities(ec); + + if (wl_display_add_socket(display, socket_name)) { + weston_log("fatal: failed to add socket: %m\n"); + ret = EXIT_FAILURE; + goto out; + } + + weston_compositor_wake(ec); + + wl_display_run(display); + + out: + /* prevent further rendering while shutting down */ + ec->state = WESTON_COMPOSITOR_OFFSCREEN; + + wl_signal_emit(&ec->destroy_signal, ec); + + for (i = ARRAY_LENGTH(signals); i;) + wl_event_source_remove(signals[--i]); + + weston_compositor_xkb_destroy(ec); + + ec->destroy(ec); + wl_display_destroy(display); + + weston_log_file_close(); + + return ret; +} diff --git a/src/compositor.h b/src/compositor.h new file mode 100644 index 00000000..0dcb604e --- /dev/null +++ b/src/compositor.h @@ -0,0 +1,1278 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2012 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _WAYLAND_SYSTEM_COMPOSITOR_H_ +#define _WAYLAND_SYSTEM_COMPOSITOR_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define WL_HIDE_DEPRECATED +#include + +#include "version.h" +#include "matrix.h" +#include "config-parser.h" +#include "zalloc.h" + +#ifndef MIN +#define MIN(x,y) (((x) < (y)) ? (x) : (y)) +#endif + +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) + +#define container_of(ptr, type, member) ({ \ + const __typeof__( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +struct weston_transform { + struct weston_matrix matrix; + struct wl_list link; +}; + +struct weston_surface; +struct weston_buffer; +struct shell_surface; +struct weston_seat; +struct weston_output; +struct input_method; + +enum weston_keyboard_modifier { + MODIFIER_CTRL = (1 << 0), + MODIFIER_ALT = (1 << 1), + MODIFIER_SUPER = (1 << 2), + MODIFIER_SHIFT = (1 << 3), +}; + +enum weston_led { + LED_NUM_LOCK = (1 << 0), + LED_CAPS_LOCK = (1 << 1), + LED_SCROLL_LOCK = (1 << 2), +}; + +struct weston_mode { + uint32_t flags; + int32_t width, height; + uint32_t refresh; + struct wl_list link; +}; + +struct weston_shell_client { + void (*send_configure)(struct weston_surface *surface, + uint32_t edges, int32_t width, int32_t height); +}; + +struct weston_shell_interface { + void *shell; /* either desktop or tablet */ + + struct shell_surface *(*create_shell_surface)(void *shell, + struct weston_surface *surface, + const struct weston_shell_client *client); + + void (*set_toplevel)(struct shell_surface *shsurf); + + void (*set_transient)(struct shell_surface *shsurf, + struct weston_surface *parent, + int x, int y, uint32_t flags); + void (*set_fullscreen)(struct shell_surface *shsurf, + uint32_t method, + uint32_t framerate, + struct weston_output *output); + void (*set_xwayland)(struct shell_surface *shsurf, + int x, int y, uint32_t flags); + int (*move)(struct shell_surface *shsurf, struct weston_seat *ws); + int (*resize)(struct shell_surface *shsurf, + struct weston_seat *ws, uint32_t edges); + void (*set_title)(struct shell_surface *shsurf, + const char *title); + +}; + +struct weston_border { + int32_t left, right, top, bottom; +}; + +struct weston_animation { + void (*frame)(struct weston_animation *animation, + struct weston_output *output, uint32_t msecs); + int frame_counter; + struct wl_list link; +}; + +enum { + WESTON_SPRING_OVERSHOOT, + WESTON_SPRING_CLAMP, + WESTON_SPRING_BOUNCE +}; + +struct weston_spring { + double k; + double friction; + double current; + double target; + double previous; + double min, max; + uint32_t timestamp; + uint32_t clip; +}; + +enum { + ZOOM_FOCUS_POINTER, + ZOOM_FOCUS_TEXT +}; + +struct weston_fixed_point { + wl_fixed_t x, y; +}; + +struct weston_output_zoom { + int active; + uint32_t type; + float increment; + float level; + float max_level; + float trans_x, trans_y; + struct weston_animation animation_z; + struct weston_spring spring_z; + struct weston_animation animation_xy; + struct weston_spring spring_xy; + struct weston_fixed_point from; + struct weston_fixed_point to; + struct weston_fixed_point current; + struct weston_fixed_point text_cursor; +}; + +/* bit compatible with drm definitions. */ +enum dpms_enum { + WESTON_DPMS_ON, + WESTON_DPMS_STANDBY, + WESTON_DPMS_SUSPEND, + WESTON_DPMS_OFF +}; + +enum weston_mode_switch_op { + WESTON_MODE_SWITCH_SET_NATIVE, + WESTON_MODE_SWITCH_SET_TEMPORARY, + WESTON_MODE_SWITCH_RESTORE_NATIVE +}; + +struct weston_output { + uint32_t id; + char *name; + + void *renderer_state; + + struct wl_list link; + struct wl_list resource_list; + struct wl_global *global; + struct weston_compositor *compositor; + struct weston_matrix matrix; + struct wl_list animation_list; + int32_t x, y, width, height; + int32_t mm_width, mm_height; + struct weston_border border; + pixman_region32_t region; + pixman_region32_t previous_damage; + int repaint_needed; + int repaint_scheduled; + struct weston_output_zoom zoom; + int dirty; + struct wl_signal frame_signal; + struct wl_signal destroy_signal; + uint32_t frame_time; + int disable_planes; + + char *make, *model, *serial_number; + uint32_t subpixel; + uint32_t transform; + int32_t native_scale; + int32_t current_scale; + int32_t original_scale; + + struct weston_mode *native_mode; + struct weston_mode *current_mode; + struct weston_mode *original_mode; + struct wl_list mode_list; + + void (*start_repaint_loop)(struct weston_output *output); + int (*repaint)(struct weston_output *output, + pixman_region32_t *damage); + void (*destroy)(struct weston_output *output); + void (*assign_planes)(struct weston_output *output); + int (*switch_mode)(struct weston_output *output, struct weston_mode *mode); + + /* backlight values are on 0-255 range, where higher is brighter */ + int32_t backlight_current; + void (*set_backlight)(struct weston_output *output, uint32_t value); + void (*set_dpms)(struct weston_output *output, enum dpms_enum level); + + int connection_internal; + uint16_t gamma_size; + void (*set_gamma)(struct weston_output *output, + uint16_t size, + uint16_t *r, + uint16_t *g, + uint16_t *b); +}; + +struct weston_pointer_grab; +struct weston_pointer_grab_interface { + void (*focus)(struct weston_pointer_grab *grab); + void (*motion)(struct weston_pointer_grab *grab, uint32_t time); + void (*button)(struct weston_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state); + void (*cancel)(struct weston_pointer_grab *grab); +}; + +struct weston_pointer_grab { + const struct weston_pointer_grab_interface *interface; + struct weston_pointer *pointer; +}; + +struct weston_keyboard_grab; +struct weston_keyboard_grab_interface { + void (*key)(struct weston_keyboard_grab *grab, uint32_t time, + uint32_t key, uint32_t state); + void (*modifiers)(struct weston_keyboard_grab *grab, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group); + void (*cancel)(struct weston_keyboard_grab *grab); +}; + +struct weston_keyboard_grab { + const struct weston_keyboard_grab_interface *interface; + struct weston_keyboard *keyboard; +}; + +struct weston_touch_grab; +struct weston_touch_grab_interface { + void (*down)(struct weston_touch_grab *grab, + uint32_t time, + int touch_id, + wl_fixed_t sx, + wl_fixed_t sy); + void (*up)(struct weston_touch_grab *grab, + uint32_t time, + int touch_id); + void (*motion)(struct weston_touch_grab *grab, + uint32_t time, + int touch_id, + wl_fixed_t sx, + wl_fixed_t sy); + void (*cancel)(struct weston_touch_grab *grab); +}; + +struct weston_touch_grab { + const struct weston_touch_grab_interface *interface; + struct weston_touch *touch; +}; + +struct weston_data_offer { + struct wl_resource *resource; + struct weston_data_source *source; + struct wl_listener source_destroy_listener; +}; + +struct weston_data_source { + struct wl_resource *resource; + struct wl_signal destroy_signal; + struct wl_array mime_types; + + void (*accept)(struct weston_data_source *source, + uint32_t serial, const char *mime_type); + void (*send)(struct weston_data_source *source, + const char *mime_type, int32_t fd); + void (*cancel)(struct weston_data_source *source); +}; + +struct weston_pointer { + struct weston_seat *seat; + + struct wl_list resource_list; + struct wl_list focus_resource_list; + struct weston_surface *focus; + uint32_t focus_serial; + struct wl_signal focus_signal; + + struct weston_surface *sprite; + struct wl_listener sprite_destroy_listener; + int32_t hotspot_x, hotspot_y; + + struct weston_pointer_grab *grab; + struct weston_pointer_grab default_grab; + wl_fixed_t grab_x, grab_y; + uint32_t grab_button; + uint32_t grab_serial; + uint32_t grab_time; + + wl_fixed_t x, y; + uint32_t button_count; +}; + + +struct weston_touch { + struct weston_seat *seat; + + struct wl_list resource_list; + struct wl_list focus_resource_list; + struct weston_surface *focus; + uint32_t focus_serial; + struct wl_signal focus_signal; + + struct weston_touch_grab *grab; + struct weston_touch_grab default_grab; + int grab_touch_id; + wl_fixed_t grab_x, grab_y; + uint32_t grab_serial; + uint32_t grab_time; +}; + +struct weston_pointer * +weston_pointer_create(void); +void +weston_pointer_destroy(struct weston_pointer *pointer); +void +weston_pointer_set_focus(struct weston_pointer *pointer, + struct weston_surface *surface, + wl_fixed_t sx, wl_fixed_t sy); +void +weston_pointer_start_grab(struct weston_pointer *pointer, + struct weston_pointer_grab *grab); +void +weston_pointer_end_grab(struct weston_pointer *pointer); +void +weston_pointer_clamp(struct weston_pointer *pointer, + wl_fixed_t *fx, wl_fixed_t *fy); + +struct weston_keyboard * +weston_keyboard_create(void); +void +weston_keyboard_destroy(struct weston_keyboard *keyboard); +void +weston_keyboard_set_focus(struct weston_keyboard *keyboard, + struct weston_surface *surface); +void +weston_keyboard_start_grab(struct weston_keyboard *device, + struct weston_keyboard_grab *grab); +void +weston_keyboard_end_grab(struct weston_keyboard *keyboard); + +struct weston_touch * +weston_touch_create(void); +void +weston_touch_destroy(struct weston_touch *touch); +void +weston_touch_set_focus(struct weston_seat *seat, + struct weston_surface *surface); +void +weston_touch_start_grab(struct weston_touch *device, + struct weston_touch_grab *grab); +void +weston_touch_end_grab(struct weston_touch *touch); + +void +wl_data_device_set_keyboard_focus(struct weston_seat *seat); + +int +wl_data_device_manager_init(struct wl_display *display); + + +void +weston_seat_set_selection(struct weston_seat *seat, + struct weston_data_source *source, uint32_t serial); +int +weston_seat_start_drag(struct weston_seat *seat, + struct weston_data_source *source, + struct weston_surface *icon, + struct wl_client *client); + +struct weston_xkb_info { + struct xkb_keymap *keymap; + int keymap_fd; + size_t keymap_size; + char *keymap_area; + int32_t ref_count; + xkb_mod_index_t shift_mod; + xkb_mod_index_t caps_mod; + xkb_mod_index_t ctrl_mod; + xkb_mod_index_t alt_mod; + xkb_mod_index_t mod2_mod; + xkb_mod_index_t mod3_mod; + xkb_mod_index_t super_mod; + xkb_mod_index_t mod5_mod; + xkb_led_index_t num_led; + xkb_led_index_t caps_led; + xkb_led_index_t scroll_led; +}; + +struct weston_keyboard { + struct weston_seat *seat; + + struct wl_list resource_list; + struct wl_list focus_resource_list; + struct weston_surface *focus; + uint32_t focus_serial; + struct wl_signal focus_signal; + + struct weston_keyboard_grab *grab; + struct weston_keyboard_grab default_grab; + uint32_t grab_key; + uint32_t grab_serial; + uint32_t grab_time; + + struct wl_array keys; + + struct { + uint32_t mods_depressed; + uint32_t mods_latched; + uint32_t mods_locked; + uint32_t group; + } modifiers; + + struct weston_keyboard_grab input_method_grab; + struct wl_resource *input_method_resource; +}; + +struct weston_seat { + struct wl_list base_resource_list; + + struct wl_global *global; + struct weston_pointer *pointer; + struct weston_keyboard *keyboard; + struct weston_touch *touch; + int pointer_device_count; + int keyboard_device_count; + int touch_device_count; + + struct weston_output *output; /* constraint */ + + struct wl_signal destroy_signal; + + struct weston_compositor *compositor; + struct wl_list link; + enum weston_keyboard_modifier modifier_state; + struct weston_surface *saved_kbd_focus; + struct wl_listener saved_kbd_focus_listener; + struct wl_list drag_resource_list; + + uint32_t selection_serial; + struct weston_data_source *selection_data_source; + struct wl_listener selection_data_source_listener; + struct wl_signal selection_signal; + + uint32_t num_tp; + + void (*led_update)(struct weston_seat *ws, enum weston_led leds); + + struct weston_xkb_info *xkb_info; + struct { + struct xkb_state *state; + enum weston_led leds; + } xkb_state; + + struct input_method *input_method; + char *seat_name; +}; + +enum { + WESTON_COMPOSITOR_ACTIVE, + WESTON_COMPOSITOR_IDLE, /* shell->unlock called on activity */ + WESTON_COMPOSITOR_OFFSCREEN, /* no rendering, no frame events */ + WESTON_COMPOSITOR_SLEEPING /* same as offscreen, but also set dmps + * to off */ +}; + +struct weston_layer { + struct wl_list surface_list; + struct wl_list link; +}; + +struct weston_plane { + struct weston_compositor *compositor; + pixman_region32_t damage; + pixman_region32_t clip; + int32_t x, y; + struct wl_list link; +}; + +struct weston_renderer { + int (*read_pixels)(struct weston_output *output, + pixman_format_code_t format, void *pixels, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height); + void (*repaint_output)(struct weston_output *output, + pixman_region32_t *output_damage); + void (*flush_damage)(struct weston_surface *surface); + void (*attach)(struct weston_surface *es, struct weston_buffer *buffer); + int (*create_surface)(struct weston_surface *surface); + void (*surface_set_color)(struct weston_surface *surface, + float red, float green, + float blue, float alpha); + void (*destroy_surface)(struct weston_surface *surface); + void (*destroy)(struct weston_compositor *ec); +}; + +enum weston_capability { + /* backend/renderer supports arbitrary rotation */ + WESTON_CAP_ROTATION_ANY = 0x0001, + + /* screencaptures need to be y-flipped */ + WESTON_CAP_CAPTURE_YFLIP = 0x0002, +}; + +struct weston_compositor { + struct wl_signal destroy_signal; + + struct wl_display *wl_display; + struct weston_shell_interface shell_interface; + struct weston_config *config; + + /* surface signals */ + struct wl_signal activate_signal; + struct wl_signal transform_signal; + + struct wl_signal kill_signal; + struct wl_signal idle_signal; + struct wl_signal wake_signal; + + struct wl_signal show_input_panel_signal; + struct wl_signal hide_input_panel_signal; + struct wl_signal update_input_panel_signal; + + struct wl_signal seat_created_signal; + struct wl_signal output_created_signal; + + struct wl_event_loop *input_loop; + struct wl_event_source *input_loop_source; + + struct wl_signal session_signal; + int session_active; + + struct weston_layer fade_layer; + struct weston_layer cursor_layer; + + struct wl_list output_list; + struct wl_list seat_list; + struct wl_list layer_list; + struct wl_list surface_list; + struct wl_list plane_list; + struct wl_list key_binding_list; + struct wl_list button_binding_list; + struct wl_list touch_binding_list; + struct wl_list axis_binding_list; + struct wl_list debug_binding_list; + + uint32_t state; + struct wl_event_source *idle_source; + uint32_t idle_inhibit; + int idle_time; /* timeout, s */ + + /* Repaint state. */ + struct weston_plane primary_plane; + uint32_t capabilities; /* combination of enum weston_capability */ + + uint32_t focus; + + struct weston_renderer *renderer; + + pixman_format_code_t read_format; + + void (*destroy)(struct weston_compositor *ec); + void (*restore)(struct weston_compositor *ec); + int (*authenticate)(struct weston_compositor *c, uint32_t id); + + void (*ping_handler)(struct weston_surface *surface, uint32_t serial); + + struct weston_launcher *launcher; + + uint32_t output_id_pool; + + struct xkb_rule_names xkb_names; + struct xkb_context *xkb_context; + struct weston_xkb_info *xkb_info; + + /* Raw keyboard processing (no libxkbcommon initialization or handling) */ + int use_xkbcommon; +}; + +struct weston_buffer { + struct wl_resource *resource; + struct wl_signal destroy_signal; + struct wl_listener destroy_listener; + + union { + struct wl_shm_buffer *shm_buffer; + void *legacy_buffer; + }; + int32_t width, height; + uint32_t busy_count; + int y_inverted; +}; + +struct weston_buffer_reference { + struct weston_buffer *buffer; + struct wl_listener destroy_listener; +}; + +struct weston_region { + struct wl_resource *resource; + pixman_region32_t region; +}; + +struct weston_subsurface { + struct wl_resource *resource; + + /* guaranteed to be valid and non-NULL */ + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; + + /* can be NULL */ + struct weston_surface *parent; + struct wl_listener parent_destroy_listener; + struct wl_list parent_link; + struct wl_list parent_link_pending; + + struct { + int32_t x; + int32_t y; + int set; + } position; + + struct { + int has_data; + + /* wl_surface.attach */ + int newly_attached; + struct weston_buffer_reference buffer_ref; + int32_t sx; + int32_t sy; + + /* wl_surface.damage */ + pixman_region32_t damage; + + /* wl_surface.set_opaque_region */ + pixman_region32_t opaque; + + /* wl_surface.set_input_region */ + pixman_region32_t input; + + /* wl_surface.frame */ + struct wl_list frame_callback_list; + + /* wl_surface.set_buffer_transform */ + uint32_t buffer_transform; + + /* wl_surface.set_buffer_scale */ + int32_t buffer_scale; + } cached; + + int synchronized; +}; + +/* Using weston_surface transformations + * + * To add a transformation to a surface, create a struct weston_transform, and + * add it to the list surface->geometry.transformation_list. Whenever you + * change the list, anything under surface->geometry, or anything in the + * weston_transforms linked into the list, you must call + * weston_surface_geometry_dirty(). + * + * The order in the list defines the order of transformations. Let the list + * contain the transformation matrices M1, ..., Mn as head to tail. The + * transformation is applied to surface-local coordinate vector p as + * P = Mn * ... * M2 * M1 * p + * to produce the global coordinate vector P. The total transform + * Mn * ... * M2 * M1 + * is cached in surface->transform.matrix, and the inverse of it in + * surface->transform.inverse. + * + * The list always contains surface->transform.position transformation, which + * is the translation by surface->geometry.x and y. + * + * If you want to apply a transformation in local coordinates, add your + * weston_transform to the head of the list. If you want to apply a + * transformation in global coordinates, add it to the tail of the list. + * + * If surface->geometry.parent is set, the total transformation of this + * surface will be the parent's total transformation and this transformation + * combined: + * Mparent * Mn * ... * M2 * M1 + */ + +struct weston_surface { + struct wl_resource *resource; + struct wl_signal destroy_signal; + struct weston_compositor *compositor; + pixman_region32_t clip; + pixman_region32_t damage; + pixman_region32_t opaque; /* part of geometry, see below */ + pixman_region32_t input; + struct wl_list link; + struct wl_list layer_link; + float alpha; /* part of geometry, see below */ + struct weston_plane *plane; + int32_t ref_count; + + void *renderer_state; + + /* Surface geometry state, mutable. + * If you change anything, call weston_surface_geometry_dirty(). + * That includes the transformations referenced from the list. + */ + struct { + float x, y; /* surface translation on display */ + int32_t width, height; + + /* struct weston_transform */ + struct wl_list transformation_list; + + /* managed by weston_surface_set_transform_parent() */ + struct weston_surface *parent; + struct wl_listener parent_destroy_listener; + struct wl_list child_list; /* geometry.parent_link */ + struct wl_list parent_link; + } geometry; + + /* State derived from geometry state, read-only. + * This is updated by weston_surface_update_transform(). + */ + struct { + int dirty; + + pixman_region32_t boundingbox; + pixman_region32_t opaque; + + /* matrix and inverse are used only if enabled = 1. + * If enabled = 0, use x, y, width, height directly. + */ + int enabled; + struct weston_matrix matrix; + struct weston_matrix inverse; + + struct weston_transform position; /* matrix from x, y */ + } transform; + + /* + * Which output to vsync this surface to. + * Used to determine, whether to send or queue frame events. + * Must be NULL, if 'link' is not in weston_compositor::surface_list. + */ + struct weston_output *output; + + /* + * A more complete representation of all outputs this surface is + * displayed on. + */ + uint32_t output_mask; + + struct wl_list frame_callback_list; + + struct weston_buffer_reference buffer_ref; + uint32_t buffer_transform; + int32_t buffer_scale; + int keep_buffer; /* bool for backends to prevent early release */ + + /* All the pending state, that wl_surface.commit will apply. */ + struct { + /* wl_surface.attach */ + int newly_attached; + struct weston_buffer *buffer; + struct wl_listener buffer_destroy_listener; + int32_t sx; + int32_t sy; + + /* wl_surface.damage */ + pixman_region32_t damage; + + /* wl_surface.set_opaque_region */ + pixman_region32_t opaque; + + /* wl_surface.set_input_region */ + pixman_region32_t input; + + /* wl_surface.frame */ + struct wl_list frame_callback_list; + + /* wl_surface.set_buffer_transform */ + uint32_t buffer_transform; + + /* wl_surface.set_scaling_factor */ + int32_t buffer_scale; + } pending; + + /* + * If non-NULL, this function will be called on surface::attach after + * a new buffer has been set up for this surface. The integer params + * are the sx and sy paramerters supplied to surface::attach . + */ + void (*configure)(struct weston_surface *es, int32_t sx, int32_t sy, int32_t width, int32_t height); + void *configure_private; + + /* Parent's list of its sub-surfaces, weston_subsurface:parent_link. + * Contains also the parent itself as a dummy weston_subsurface, + * if the list is not empty. + */ + struct wl_list subsurface_list; /* weston_subsurface::parent_link */ + struct wl_list subsurface_list_pending; /* ...::parent_link_pending */ +}; + +enum weston_key_state_update { + STATE_UPDATE_AUTOMATIC, + STATE_UPDATE_NONE, +}; + +void +weston_version(int *major, int *minor, int *micro); + +void +weston_surface_update_transform(struct weston_surface *surface); + +void +weston_surface_geometry_dirty(struct weston_surface *surface); + +void +weston_surface_to_global_fixed(struct weston_surface *surface, + wl_fixed_t sx, wl_fixed_t sy, + wl_fixed_t *x, wl_fixed_t *y); +void +weston_surface_to_global_float(struct weston_surface *surface, + float sx, float sy, float *x, float *y); + +void +weston_surface_from_global_float(struct weston_surface *surface, + float x, float y, float *sx, float *sy); +void +weston_surface_from_global(struct weston_surface *surface, + int32_t x, int32_t y, int32_t *sx, int32_t *sy); +void +weston_surface_from_global_fixed(struct weston_surface *surface, + wl_fixed_t x, wl_fixed_t y, + wl_fixed_t *sx, wl_fixed_t *sy); +int32_t +weston_surface_buffer_width(struct weston_surface *surface); +int32_t +weston_surface_buffer_height(struct weston_surface *surface); + +WL_EXPORT void +weston_surface_to_buffer_float(struct weston_surface *surface, + float x, float y, float *bx, float *by); +WL_EXPORT void +weston_surface_to_buffer(struct weston_surface *surface, + int sx, int sy, int *bx, int *by); + +pixman_box32_t +weston_surface_to_buffer_rect(struct weston_surface *surface, + pixman_box32_t rect); + +void +weston_spring_init(struct weston_spring *spring, + double k, double current, double target); +void +weston_spring_update(struct weston_spring *spring, uint32_t msec); +int +weston_spring_done(struct weston_spring *spring); + +void +weston_surface_activate(struct weston_surface *surface, + struct weston_seat *seat); +void +notify_motion(struct weston_seat *seat, uint32_t time, + wl_fixed_t dx, wl_fixed_t dy); +void +notify_motion_absolute(struct weston_seat *seat, uint32_t time, + wl_fixed_t x, wl_fixed_t y); +void +notify_button(struct weston_seat *seat, uint32_t time, int32_t button, + enum wl_pointer_button_state state); +void +notify_axis(struct weston_seat *seat, uint32_t time, uint32_t axis, + wl_fixed_t value); +void +notify_key(struct weston_seat *seat, uint32_t time, uint32_t key, + enum wl_keyboard_key_state state, + enum weston_key_state_update update_state); +void +notify_modifiers(struct weston_seat *seat, uint32_t serial); + +void +notify_pointer_focus(struct weston_seat *seat, struct weston_output *output, + wl_fixed_t x, wl_fixed_t y); + +void +notify_keyboard_focus_in(struct weston_seat *seat, struct wl_array *keys, + enum weston_key_state_update update_state); +void +notify_keyboard_focus_out(struct weston_seat *seat); + +void +notify_touch(struct weston_seat *seat, uint32_t time, int touch_id, + wl_fixed_t x, wl_fixed_t y, int touch_type); + +void +weston_layer_init(struct weston_layer *layer, struct wl_list *below); + +void +weston_plane_init(struct weston_plane *plane, + struct weston_compositor *ec, + int32_t x, int32_t y); +void +weston_plane_release(struct weston_plane *plane); + +void +weston_compositor_stack_plane(struct weston_compositor *ec, + struct weston_plane *plane, + struct weston_plane *above); + +void +weston_output_finish_frame(struct weston_output *output, uint32_t msecs); +void +weston_output_schedule_repaint(struct weston_output *output); +void +weston_output_damage(struct weston_output *output); +void +weston_compositor_schedule_repaint(struct weston_compositor *compositor); +void +weston_compositor_fade(struct weston_compositor *compositor, float tint); +void +weston_compositor_damage_all(struct weston_compositor *compositor); +void +weston_compositor_unlock(struct weston_compositor *compositor); +void +weston_compositor_wake(struct weston_compositor *compositor); +void +weston_compositor_offscreen(struct weston_compositor *compositor); +void +weston_compositor_sleep(struct weston_compositor *compositor); +struct weston_surface * +weston_compositor_pick_surface(struct weston_compositor *compositor, + wl_fixed_t x, wl_fixed_t y, + wl_fixed_t *sx, wl_fixed_t *sy); + + +struct weston_binding; +typedef void (*weston_key_binding_handler_t)(struct weston_seat *seat, + uint32_t time, uint32_t key, + void *data); +struct weston_binding * +weston_compositor_add_key_binding(struct weston_compositor *compositor, + uint32_t key, + enum weston_keyboard_modifier modifier, + weston_key_binding_handler_t binding, + void *data); + +typedef void (*weston_button_binding_handler_t)(struct weston_seat *seat, + uint32_t time, uint32_t button, + void *data); +struct weston_binding * +weston_compositor_add_button_binding(struct weston_compositor *compositor, + uint32_t button, + enum weston_keyboard_modifier modifier, + weston_button_binding_handler_t binding, + void *data); + +typedef void (*weston_touch_binding_handler_t)(struct weston_seat *seat, + uint32_t time, + void *data); +struct weston_binding * +weston_compositor_add_touch_binding(struct weston_compositor *compositor, + enum weston_keyboard_modifier modifier, + weston_touch_binding_handler_t binding, + void *data); + +typedef void (*weston_axis_binding_handler_t)(struct weston_seat *seat, + uint32_t time, uint32_t axis, + wl_fixed_t value, void *data); +struct weston_binding * +weston_compositor_add_axis_binding(struct weston_compositor *compositor, + uint32_t axis, + enum weston_keyboard_modifier modifier, + weston_axis_binding_handler_t binding, + void *data); +struct weston_binding * +weston_compositor_add_debug_binding(struct weston_compositor *compositor, + uint32_t key, + weston_key_binding_handler_t binding, + void *data); +void +weston_binding_destroy(struct weston_binding *binding); + +void +weston_binding_list_destroy_all(struct wl_list *list); + +void +weston_compositor_run_key_binding(struct weston_compositor *compositor, + struct weston_seat *seat, uint32_t time, + uint32_t key, + enum wl_keyboard_key_state state); +void +weston_compositor_run_button_binding(struct weston_compositor *compositor, + struct weston_seat *seat, uint32_t time, + uint32_t button, + enum wl_pointer_button_state value); +void +weston_compositor_run_touch_binding(struct weston_compositor *compositor, + struct weston_seat *seat, uint32_t time, + int touch_type); +int +weston_compositor_run_axis_binding(struct weston_compositor *compositor, + struct weston_seat *seat, uint32_t time, + uint32_t axis, int32_t value); +int +weston_compositor_run_debug_binding(struct weston_compositor *compositor, + struct weston_seat *seat, uint32_t time, + uint32_t key, + enum wl_keyboard_key_state state); + +int +weston_environment_get_fd(const char *env); + +struct wl_list * +weston_compositor_top(struct weston_compositor *compositor); + +struct weston_surface * +weston_surface_create(struct weston_compositor *compositor); + +void +weston_surface_configure(struct weston_surface *surface, + float x, float y, int width, int height); + +void +weston_surface_restack(struct weston_surface *surface, struct wl_list *below); + +void +weston_surface_set_position(struct weston_surface *surface, + float x, float y); + +void +weston_surface_set_transform_parent(struct weston_surface *surface, + struct weston_surface *parent); + +int +weston_surface_is_mapped(struct weston_surface *surface); + +void +weston_surface_schedule_repaint(struct weston_surface *surface); + +void +weston_surface_damage(struct weston_surface *surface); + +void +weston_surface_damage_below(struct weston_surface *surface); + +void +weston_surface_move_to_plane(struct weston_surface *surface, + struct weston_plane *plane); +void +weston_surface_unmap(struct weston_surface *surface); + +struct weston_surface * +weston_surface_get_main_surface(struct weston_surface *surface); + +struct weston_buffer * +weston_buffer_from_resource(struct wl_resource *resource); + +void +weston_buffer_reference(struct weston_buffer_reference *ref, + struct weston_buffer *buffer); + +uint32_t +weston_compositor_get_time(void); + +int +weston_compositor_init(struct weston_compositor *ec, struct wl_display *display, + int *argc, char *argv[], struct weston_config *config); +void +weston_compositor_shutdown(struct weston_compositor *ec); +void +weston_text_cursor_position_notify(struct weston_surface *surface, + wl_fixed_t x, wl_fixed_t y); +void +weston_output_init_zoom(struct weston_output *output); +void +weston_output_update_zoom(struct weston_output *output, uint32_t type); +void +weston_output_update_matrix(struct weston_output *output); +void +weston_output_move(struct weston_output *output, int x, int y); +void +weston_output_init(struct weston_output *output, struct weston_compositor *c, + int x, int y, int width, int height, uint32_t transform, int32_t scale); +void +weston_output_destroy(struct weston_output *output); +void +weston_output_transform_coordinate(struct weston_output *x11_output, + int device_x, int device_y, + wl_fixed_t *x, wl_fixed_t *y); + +void +weston_seat_init(struct weston_seat *seat, struct weston_compositor *ec, + const char *seat_name); +void +weston_seat_init_pointer(struct weston_seat *seat); +void +weston_seat_release_pointer(struct weston_seat *seat); +int +weston_seat_init_keyboard(struct weston_seat *seat, struct xkb_keymap *keymap); +void +weston_seat_release_keyboard(struct weston_seat *seat); +void +weston_seat_init_touch(struct weston_seat *seat); +void +weston_seat_release_touch(struct weston_seat *seat); +void +weston_seat_repick(struct weston_seat *seat); + +void +weston_seat_release(struct weston_seat *seat); +int +weston_compositor_xkb_init(struct weston_compositor *ec, + struct xkb_rule_names *names); +void +weston_compositor_xkb_destroy(struct weston_compositor *ec); + +/* String literal of spaces, the same width as the timestamp. */ +#define STAMP_SPACE " " + +void +weston_log_file_open(const char *filename); +void +weston_log_file_close(void); +int +weston_vlog(const char *fmt, va_list ap); +int +weston_vlog_continue(const char *fmt, va_list ap); +int +weston_log(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); +int +weston_log_continue(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +enum { + TTY_ENTER_VT, + TTY_LEAVE_VT +}; + +struct tty * +tty_create(struct weston_compositor *compositor, int tty_nr); + +void +tty_destroy(struct tty *tty); + +void +tty_reset(struct tty *tty); + +int +tty_activate_vt(struct tty *tty, int vt); + +void +screenshooter_create(struct weston_compositor *ec); + +struct clipboard * +clipboard_create(struct weston_seat *seat); + +void +text_cursor_position_notifier_create(struct weston_compositor *ec); + +int +text_backend_init(struct weston_compositor *ec); + +struct weston_process; +typedef void (*weston_process_cleanup_func_t)(struct weston_process *process, + int status); + +struct weston_process { + pid_t pid; + weston_process_cleanup_func_t cleanup; + struct wl_list link; +}; + +struct wl_client * +weston_client_launch(struct weston_compositor *compositor, + struct weston_process *proc, + const char *path, + weston_process_cleanup_func_t cleanup); + +void +weston_watch_process(struct weston_process *process); + +struct weston_surface_animation; +typedef void (*weston_surface_animation_done_func_t)(struct weston_surface_animation *animation, void *data); + +struct weston_surface_animation * +weston_zoom_run(struct weston_surface *surface, float start, float stop, + weston_surface_animation_done_func_t done, void *data); + +struct weston_surface_animation * +weston_fade_run(struct weston_surface *surface, + float start, float end, float k, + weston_surface_animation_done_func_t done, void *data); +void +weston_fade_update(struct weston_surface_animation *fade, float target); + +struct weston_surface_animation * +weston_slide_run(struct weston_surface *surface, float start, float stop, + weston_surface_animation_done_func_t done, void *data); + +void +weston_surface_set_color(struct weston_surface *surface, + float red, float green, float blue, float alpha); + +void +weston_surface_destroy(struct weston_surface *surface); + +int +weston_output_switch_mode(struct weston_output *output, struct weston_mode *mode, + int32_t scale, enum weston_mode_switch_op op); + +int +noop_renderer_init(struct weston_compositor *ec); + +struct weston_compositor * +backend_init(struct wl_display *display, int *argc, char *argv[], + struct weston_config *config); + +int +module_init(struct weston_compositor *compositor, + int *argc, char *argv[]); + +void +weston_transformed_coord(int width, int height, + enum wl_output_transform transform, + int32_t scale, + float sx, float sy, float *bx, float *by); +pixman_box32_t +weston_transformed_rect(int width, int height, + enum wl_output_transform transform, + int32_t scale, + pixman_box32_t rect); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/data-device.c b/src/data-device.c new file mode 100644 index 00000000..c3dc0bda --- /dev/null +++ b/src/data-device.c @@ -0,0 +1,677 @@ +/* + * Copyright © 2011 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "compositor.h" + +struct weston_drag { + struct wl_client *client; + struct weston_data_source *data_source; + struct wl_listener data_source_listener; + struct weston_surface *focus; + struct wl_resource *focus_resource; + struct wl_listener focus_listener; + struct weston_pointer_grab grab; + struct weston_surface *icon; + struct wl_listener icon_destroy_listener; + int32_t dx, dy; +}; + +static void +empty_region(pixman_region32_t *region) +{ + pixman_region32_fini(region); + pixman_region32_init(region); +} + +static void +data_offer_accept(struct wl_client *client, struct wl_resource *resource, + uint32_t serial, const char *mime_type) +{ + struct weston_data_offer *offer = wl_resource_get_user_data(resource); + + /* FIXME: Check that client is currently focused by the input + * device that is currently dragging this data source. Should + * this be a wl_data_device request? */ + + if (offer->source) + offer->source->accept(offer->source, serial, mime_type); +} + +static void +data_offer_receive(struct wl_client *client, struct wl_resource *resource, + const char *mime_type, int32_t fd) +{ + struct weston_data_offer *offer = wl_resource_get_user_data(resource); + + if (offer->source) + offer->source->send(offer->source, mime_type, fd); + else + close(fd); +} + +static void +data_offer_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_data_offer_interface data_offer_interface = { + data_offer_accept, + data_offer_receive, + data_offer_destroy, +}; + +static void +destroy_data_offer(struct wl_resource *resource) +{ + struct weston_data_offer *offer = wl_resource_get_user_data(resource); + + if (offer->source) + wl_list_remove(&offer->source_destroy_listener.link); + free(offer); +} + +static void +destroy_offer_data_source(struct wl_listener *listener, void *data) +{ + struct weston_data_offer *offer; + + offer = container_of(listener, struct weston_data_offer, + source_destroy_listener); + + offer->source = NULL; +} + +static struct wl_resource * +weston_data_source_send_offer(struct weston_data_source *source, + struct wl_resource *target) +{ + struct weston_data_offer *offer; + char **p; + + offer = malloc(sizeof *offer); + if (offer == NULL) + return NULL; + + offer->resource = + wl_resource_create(wl_resource_get_client(target), + &wl_data_offer_interface, 1, 0); + if (offer->resource == NULL) { + free(offer); + return NULL; + } + + wl_resource_set_implementation(offer->resource, &data_offer_interface, + offer, destroy_data_offer); + + offer->source = source; + offer->source_destroy_listener.notify = destroy_offer_data_source; + wl_signal_add(&source->destroy_signal, + &offer->source_destroy_listener); + + wl_data_device_send_data_offer(target, offer->resource); + + wl_array_for_each(p, &source->mime_types) + wl_data_offer_send_offer(offer->resource, *p); + + return offer->resource; +} + +static void +data_source_offer(struct wl_client *client, + struct wl_resource *resource, + const char *type) +{ + struct weston_data_source *source = + wl_resource_get_user_data(resource); + char **p; + + p = wl_array_add(&source->mime_types, sizeof *p); + if (p) + *p = strdup(type); + if (!p || !*p) + wl_resource_post_no_memory(resource); +} + +static void +data_source_destroy(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static struct wl_data_source_interface data_source_interface = { + data_source_offer, + data_source_destroy +}; + +static void +drag_surface_configure(struct weston_surface *es, int32_t sx, int32_t sy, int32_t width, int32_t height) +{ + struct weston_drag *drag = es->configure_private; + struct weston_pointer *pointer = drag->grab.pointer; + struct wl_list *list; + float fx, fy; + + if (!weston_surface_is_mapped(es) && es->buffer_ref.buffer) { + if (pointer->sprite && weston_surface_is_mapped(pointer->sprite)) + list = &pointer->sprite->layer_link; + else + list = &es->compositor->cursor_layer.surface_list; + + wl_list_insert(list, &es->layer_link); + weston_surface_update_transform(es); + empty_region(&es->pending.input); + } + + drag->dx += sx; + drag->dy += sy; + + fx = wl_fixed_to_double(pointer->x) + drag->dx; + fy = wl_fixed_to_double(pointer->y) + drag->dy; + weston_surface_configure(es, fx, fy, width, height); +} + +static void +destroy_drag_focus(struct wl_listener *listener, void *data) +{ + struct weston_drag *drag = + container_of(listener, struct weston_drag, focus_listener); + + drag->focus_resource = NULL; +} + +static void +weston_drag_set_focus(struct weston_drag *drag, struct weston_surface *surface, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct weston_pointer *pointer = drag->grab.pointer; + struct wl_resource *resource, *offer = NULL; + struct wl_display *display = pointer->seat->compositor->wl_display; + uint32_t serial; + + if (drag->focus_resource) { + wl_data_device_send_leave(drag->focus_resource); + wl_list_remove(&drag->focus_listener.link); + drag->focus_resource = NULL; + drag->focus = NULL; + } + + if (!surface) + return; + + if (!drag->data_source && + wl_resource_get_client(surface->resource) != drag->client) + return; + + resource = wl_resource_find_for_client(&pointer->seat->drag_resource_list, + wl_resource_get_client(surface->resource)); + if (!resource) + return; + + serial = wl_display_next_serial(display); + + if (drag->data_source) { + offer = weston_data_source_send_offer(drag->data_source, + resource); + if (offer == NULL) + return; + } + + wl_data_device_send_enter(resource, serial, surface->resource, + sx, sy, offer); + + drag->focus = surface; + drag->focus_listener.notify = destroy_drag_focus; + wl_resource_add_destroy_listener(resource, &drag->focus_listener); + drag->focus_resource = resource; +} + +static void +drag_grab_focus(struct weston_pointer_grab *grab) +{ + struct weston_drag *drag = + container_of(grab, struct weston_drag, grab); + struct weston_pointer *pointer = grab->pointer; + struct weston_surface *surface; + wl_fixed_t sx, sy; + + surface = weston_compositor_pick_surface(pointer->seat->compositor, + pointer->x, pointer->y, + &sx, &sy); + if (drag->focus != surface) + weston_drag_set_focus(drag, surface, sx, sy); +} + +static void +drag_grab_motion(struct weston_pointer_grab *grab, uint32_t time) +{ + struct weston_drag *drag = + container_of(grab, struct weston_drag, grab); + struct weston_pointer *pointer = drag->grab.pointer; + float fx, fy; + wl_fixed_t sx, sy; + + if (drag->icon) { + fx = wl_fixed_to_double(pointer->x) + drag->dx; + fy = wl_fixed_to_double(pointer->y) + drag->dy; + weston_surface_set_position(drag->icon, fx, fy); + weston_surface_schedule_repaint(drag->icon); + } + + if (drag->focus_resource) { + weston_surface_from_global_fixed(drag->focus, + pointer->x, pointer->y, + &sx, &sy); + + wl_data_device_send_motion(drag->focus_resource, time, sx, sy); + } +} + +static void +data_device_end_drag_grab(struct weston_drag *drag) +{ + if (drag->icon) { + if (weston_surface_is_mapped(drag->icon)) + weston_surface_unmap(drag->icon); + + drag->icon->configure = NULL; + empty_region(&drag->icon->pending.input); + wl_list_remove(&drag->icon_destroy_listener.link); + } + + weston_drag_set_focus(drag, NULL, 0, 0); + + weston_pointer_end_grab(drag->grab.pointer); + + free(drag); +} + +static void +drag_grab_button(struct weston_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state_w) +{ + struct weston_drag *drag = + container_of(grab, struct weston_drag, grab); + struct weston_pointer *pointer = drag->grab.pointer; + enum wl_pointer_button_state state = state_w; + + if (drag->focus_resource && + pointer->grab_button == button && + state == WL_POINTER_BUTTON_STATE_RELEASED) + wl_data_device_send_drop(drag->focus_resource); + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + if (drag->data_source) + wl_list_remove(&drag->data_source_listener.link); + data_device_end_drag_grab(drag); + } +} + +static void +drag_grab_cancel(struct weston_pointer_grab *grab) +{ + struct weston_drag *drag = + container_of(grab, struct weston_drag, grab); + + if (drag->data_source) + wl_list_remove(&drag->data_source_listener.link); + + data_device_end_drag_grab(drag); +} + +static const struct weston_pointer_grab_interface drag_grab_interface = { + drag_grab_focus, + drag_grab_motion, + drag_grab_button, + drag_grab_cancel, +}; + +static void +destroy_data_device_source(struct wl_listener *listener, void *data) +{ + struct weston_drag *drag = container_of(listener, struct weston_drag, + data_source_listener); + + data_device_end_drag_grab(drag); +} + +static void +handle_drag_icon_destroy(struct wl_listener *listener, void *data) +{ + struct weston_drag *drag = container_of(listener, struct weston_drag, + icon_destroy_listener); + + drag->icon = NULL; +} + +WL_EXPORT int +weston_seat_start_drag(struct weston_seat *seat, + struct weston_data_source *source, + struct weston_surface *icon, + struct wl_client *client) +{ + struct weston_drag *drag; + + drag = zalloc(sizeof *drag); + if (drag == NULL) + return -1; + + drag->grab.interface = &drag_grab_interface; + drag->client = client; + drag->data_source = source; + drag->icon = icon; + + if (source) { + drag->data_source_listener.notify = destroy_data_device_source; + wl_signal_add(&source->destroy_signal, + &drag->data_source_listener); + } + + if (icon) { + drag->icon_destroy_listener.notify = handle_drag_icon_destroy; + wl_signal_add(&icon->destroy_signal, + &drag->icon_destroy_listener); + + icon->configure = drag_surface_configure; + icon->configure_private = drag; + } + + weston_pointer_set_focus(seat->pointer, NULL, + wl_fixed_from_int(0), wl_fixed_from_int(0)); + weston_pointer_start_grab(seat->pointer, &drag->grab); + + return 0; +} + +static void +data_device_start_drag(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *source_resource, + struct wl_resource *origin_resource, + struct wl_resource *icon_resource, uint32_t serial) +{ + struct weston_seat *seat = wl_resource_get_user_data(resource); + struct weston_data_source *source = NULL; + struct weston_surface *icon = NULL; + + if (seat->pointer->button_count == 0 || + seat->pointer->grab_serial != serial || + seat->pointer->focus != wl_resource_get_user_data(origin_resource)) + return; + + /* FIXME: Check that the data source type array isn't empty. */ + + if (source_resource) + source = wl_resource_get_user_data(source_resource); + if (icon_resource) + icon = wl_resource_get_user_data(icon_resource); + if (icon && icon->configure) { + wl_resource_post_error(icon_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface->configure already set"); + return; + } + + if (weston_seat_start_drag(seat, source, icon, client) < 0) + wl_resource_post_no_memory(resource); +} + +static void +destroy_selection_data_source(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = container_of(listener, struct weston_seat, + selection_data_source_listener); + struct wl_resource *data_device; + struct weston_surface *focus = NULL; + + seat->selection_data_source = NULL; + + if (seat->keyboard) + focus = seat->keyboard->focus; + if (focus && focus->resource) { + data_device = wl_resource_find_for_client(&seat->drag_resource_list, + wl_resource_get_client(focus->resource)); + if (data_device) + wl_data_device_send_selection(data_device, NULL); + } + + wl_signal_emit(&seat->selection_signal, seat); +} + +WL_EXPORT void +weston_seat_set_selection(struct weston_seat *seat, + struct weston_data_source *source, uint32_t serial) +{ + struct wl_resource *data_device, *offer; + struct weston_surface *focus = NULL; + + if (seat->selection_data_source && + seat->selection_serial - serial < UINT32_MAX / 2) + return; + + if (seat->selection_data_source) { + seat->selection_data_source->cancel(seat->selection_data_source); + wl_list_remove(&seat->selection_data_source_listener.link); + seat->selection_data_source = NULL; + } + + seat->selection_data_source = source; + seat->selection_serial = serial; + + if (seat->keyboard) + focus = seat->keyboard->focus; + if (focus && focus->resource) { + data_device = wl_resource_find_for_client(&seat->drag_resource_list, + wl_resource_get_client(focus->resource)); + if (data_device && source) { + offer = weston_data_source_send_offer(seat->selection_data_source, + data_device); + wl_data_device_send_selection(data_device, offer); + } else if (data_device) { + wl_data_device_send_selection(data_device, NULL); + } + } + + wl_signal_emit(&seat->selection_signal, seat); + + if (source) { + seat->selection_data_source_listener.notify = + destroy_selection_data_source; + wl_signal_add(&source->destroy_signal, + &seat->selection_data_source_listener); + } +} + +static void +data_device_set_selection(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *source_resource, uint32_t serial) +{ + if (!source_resource) + return; + + /* FIXME: Store serial and check against incoming serial here. */ + weston_seat_set_selection(wl_resource_get_user_data(resource), + wl_resource_get_user_data(source_resource), + serial); +} + +static const struct wl_data_device_interface data_device_interface = { + data_device_start_drag, + data_device_set_selection, +}; + +static void +destroy_data_source(struct wl_resource *resource) +{ + struct weston_data_source *source = + wl_resource_get_user_data(resource); + char **p; + + wl_signal_emit(&source->destroy_signal, source); + + wl_array_for_each(p, &source->mime_types) + free(*p); + + wl_array_release(&source->mime_types); + + free(source); +} + +static void +client_source_accept(struct weston_data_source *source, + uint32_t time, const char *mime_type) +{ + wl_data_source_send_target(source->resource, mime_type); +} + +static void +client_source_send(struct weston_data_source *source, + const char *mime_type, int32_t fd) +{ + wl_data_source_send_send(source->resource, mime_type, fd); + close(fd); +} + +static void +client_source_cancel(struct weston_data_source *source) +{ + wl_data_source_send_cancelled(source->resource); +} + +static void +create_data_source(struct wl_client *client, + struct wl_resource *resource, uint32_t id) +{ + struct weston_data_source *source; + + source = malloc(sizeof *source); + if (source == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + wl_signal_init(&source->destroy_signal); + source->accept = client_source_accept; + source->send = client_source_send; + source->cancel = client_source_cancel; + + wl_array_init(&source->mime_types); + + source->resource = + wl_resource_create(client, &wl_data_source_interface, 1, id); + wl_resource_set_implementation(source->resource, &data_source_interface, + source, destroy_data_source); +} + +static void unbind_data_device(struct wl_resource *resource) +{ + wl_list_remove(wl_resource_get_link(resource)); +} + +static void +get_data_device(struct wl_client *client, + struct wl_resource *manager_resource, + uint32_t id, struct wl_resource *seat_resource) +{ + struct weston_seat *seat = wl_resource_get_user_data(seat_resource); + struct wl_resource *resource; + + resource = wl_resource_create(client, + &wl_data_device_interface, 1, id); + if (resource == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + wl_list_insert(&seat->drag_resource_list, + wl_resource_get_link(resource)); + wl_resource_set_implementation(resource, &data_device_interface, + seat, unbind_data_device); +} + +static const struct wl_data_device_manager_interface manager_interface = { + create_data_source, + get_data_device +}; + +static void +bind_manager(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + + resource = + wl_resource_create(client, + &wl_data_device_manager_interface, 1, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &manager_interface, + NULL, NULL); +} + +WL_EXPORT void +wl_data_device_set_keyboard_focus(struct weston_seat *seat) +{ + struct wl_resource *data_device, *offer; + struct weston_data_source *source; + struct weston_surface *focus; + + if (!seat->keyboard) + return; + + focus = seat->keyboard->focus; + if (!focus || !focus->resource) + return; + + data_device = wl_resource_find_for_client(&seat->drag_resource_list, + wl_resource_get_client(focus->resource)); + if (!data_device) + return; + + source = seat->selection_data_source; + if (source) { + offer = weston_data_source_send_offer(source, data_device); + wl_data_device_send_selection(data_device, offer); + } +} + +WL_EXPORT int +wl_data_device_manager_init(struct wl_display *display) +{ + if (wl_global_create(display, + &wl_data_device_manager_interface, 1, + NULL, bind_manager) == NULL) + return -1; + + return 0; +} diff --git a/src/evdev-touchpad.c b/src/evdev-touchpad.c new file mode 100644 index 00000000..69f913ac --- /dev/null +++ b/src/evdev-touchpad.c @@ -0,0 +1,800 @@ +/* + * Copyright © 2012 Jonas Ådahl + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "filter.h" +#include "evdev.h" +#include "../shared/config-parser.h" + +/* Default values */ +#define DEFAULT_CONSTANT_ACCEL_NUMERATOR 50 +#define DEFAULT_MIN_ACCEL_FACTOR 0.16 +#define DEFAULT_MAX_ACCEL_FACTOR 1.0 +#define DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR 700.0 + +#define DEFAULT_TOUCHPAD_SINGLE_TAP_BUTTON BTN_LEFT +#define DEFAULT_TOUCHPAD_SINGLE_TAP_TIMEOUT 100 + +enum touchpad_model { + TOUCHPAD_MODEL_UNKNOWN = 0, + TOUCHPAD_MODEL_SYNAPTICS, + TOUCHPAD_MODEL_ALPS, + TOUCHPAD_MODEL_APPLETOUCH, + TOUCHPAD_MODEL_ELANTECH +}; + +enum touchpad_event { + TOUCHPAD_EVENT_NONE = 0, + TOUCHPAD_EVENT_ABSOLUTE_ANY = (1 << 0), + TOUCHPAD_EVENT_ABSOLUTE_X = (1 << 1), + TOUCHPAD_EVENT_ABSOLUTE_Y = (1 << 2), + TOUCHPAD_EVENT_REPORT = (1 << 3) +}; + +struct touchpad_model_spec { + short vendor; + short product; + enum touchpad_model model; +}; + +static struct touchpad_model_spec touchpad_spec_table[] = { + {0x0002, 0x0007, TOUCHPAD_MODEL_SYNAPTICS}, + {0x0002, 0x0008, TOUCHPAD_MODEL_ALPS}, + {0x05ac, 0x0000, TOUCHPAD_MODEL_APPLETOUCH}, + {0x0002, 0x000e, TOUCHPAD_MODEL_ELANTECH}, + {0x0000, 0x0000, TOUCHPAD_MODEL_UNKNOWN} +}; + +enum touchpad_state { + TOUCHPAD_STATE_NONE = 0, + TOUCHPAD_STATE_TOUCH = (1 << 0), + TOUCHPAD_STATE_MOVE = (1 << 1) +}; + +#define TOUCHPAD_HISTORY_LENGTH 4 + +struct touchpad_motion { + int32_t x; + int32_t y; +}; + +enum touchpad_fingers_state { + TOUCHPAD_FINGERS_ONE = (1 << 0), + TOUCHPAD_FINGERS_TWO = (1 << 1), + TOUCHPAD_FINGERS_THREE = (1 << 2) +}; + +enum fsm_event { + FSM_EVENT_TOUCH, + FSM_EVENT_RELEASE, + FSM_EVENT_MOTION, + FSM_EVENT_TIMEOUT +}; + +enum fsm_state { + FSM_IDLE, + FSM_TOUCH, + FSM_TAP, + FSM_TAP_2, + FSM_DRAG +}; + +struct touchpad_dispatch { + struct evdev_dispatch base; + struct evdev_device *device; + + enum touchpad_model model; + unsigned int state; + int finger_state; + int last_finger_state; + + double constant_accel_factor; + double min_accel_factor; + double max_accel_factor; + + unsigned int event_mask; + unsigned int event_mask_filter; + + int reset; + + struct { + bool enable; + + struct wl_array events; + enum fsm_state state; + struct wl_event_source *timer_source; + } fsm; + + struct { + int32_t x; + int32_t y; + } hw_abs; + + int has_pressure; + struct { + int32_t touch_low; + int32_t touch_high; + } pressure; + + struct { + int32_t margin_x; + int32_t margin_y; + int32_t center_x; + int32_t center_y; + } hysteresis; + + struct touchpad_motion motion_history[TOUCHPAD_HISTORY_LENGTH]; + int motion_index; + unsigned int motion_count; + + struct weston_motion_filter *filter; +}; + +static enum touchpad_model +get_touchpad_model(struct evdev_device *device) +{ + struct input_id id; + unsigned int i; + + if (ioctl(device->fd, EVIOCGID, &id) < 0) + return TOUCHPAD_MODEL_UNKNOWN; + + for (i = 0; i < ARRAY_LENGTH(touchpad_spec_table); i++) + if (touchpad_spec_table[i].vendor == id.vendor && + (!touchpad_spec_table[i].product || + touchpad_spec_table[i].product == id.product)) + return touchpad_spec_table[i].model; + + return TOUCHPAD_MODEL_UNKNOWN; +} + +static void +configure_touchpad_pressure(struct touchpad_dispatch *touchpad, + int32_t pressure_min, int32_t pressure_max) +{ + int32_t range = pressure_max - pressure_min + 1; + + touchpad->has_pressure = 1; + + /* Magic numbers from xf86-input-synaptics */ + switch (touchpad->model) { + case TOUCHPAD_MODEL_ELANTECH: + touchpad->pressure.touch_low = pressure_min + 1; + touchpad->pressure.touch_high = pressure_min + 1; + break; + default: + touchpad->pressure.touch_low = + pressure_min + range * (25.0/256.0); + touchpad->pressure.touch_high = + pressure_min + range * (30.0/256.0); + } +} + +static double +touchpad_profile(struct weston_motion_filter *filter, + void *data, + double velocity, + uint32_t time) +{ + struct touchpad_dispatch *touchpad = + (struct touchpad_dispatch *) data; + + double accel_factor; + + accel_factor = velocity * touchpad->constant_accel_factor; + + if (accel_factor > touchpad->max_accel_factor) + accel_factor = touchpad->max_accel_factor; + else if (accel_factor < touchpad->min_accel_factor) + accel_factor = touchpad->min_accel_factor; + + return accel_factor; +} + +static inline struct touchpad_motion * +motion_history_offset(struct touchpad_dispatch *touchpad, int offset) +{ + int offset_index = + (touchpad->motion_index - offset + TOUCHPAD_HISTORY_LENGTH) % + TOUCHPAD_HISTORY_LENGTH; + + return &touchpad->motion_history[offset_index]; +} + +static double +estimate_delta(int x0, int x1, int x2, int x3) +{ + return (x0 + x1 - x2 - x3) / 4; +} + +static int +hysteresis(int in, int center, int margin) +{ + int diff = in - center; + if (abs(diff) <= margin) + return center; + + if (diff > margin) + return center + diff - margin; + else if (diff < -margin) + return center + diff + margin; + return center + diff; +} + +static void +touchpad_get_delta(struct touchpad_dispatch *touchpad, double *dx, double *dy) +{ + *dx = estimate_delta(motion_history_offset(touchpad, 0)->x, + motion_history_offset(touchpad, 1)->x, + motion_history_offset(touchpad, 2)->x, + motion_history_offset(touchpad, 3)->x); + *dy = estimate_delta(motion_history_offset(touchpad, 0)->y, + motion_history_offset(touchpad, 1)->y, + motion_history_offset(touchpad, 2)->y, + motion_history_offset(touchpad, 3)->y); +} + +static void +filter_motion(struct touchpad_dispatch *touchpad, + double *dx, double *dy, uint32_t time) +{ + struct weston_motion_params motion; + + motion.dx = *dx; + motion.dy = *dy; + + weston_filter_dispatch(touchpad->filter, &motion, touchpad, time); + + *dx = motion.dx; + *dy = motion.dy; +} + +static void +notify_button_pressed(struct touchpad_dispatch *touchpad, uint32_t time) +{ + notify_button(touchpad->device->seat, time, + DEFAULT_TOUCHPAD_SINGLE_TAP_BUTTON, + WL_POINTER_BUTTON_STATE_PRESSED); +} + +static void +notify_button_released(struct touchpad_dispatch *touchpad, uint32_t time) +{ + notify_button(touchpad->device->seat, time, + DEFAULT_TOUCHPAD_SINGLE_TAP_BUTTON, + WL_POINTER_BUTTON_STATE_RELEASED); +} + +static void +notify_tap(struct touchpad_dispatch *touchpad, uint32_t time) +{ + notify_button_pressed(touchpad, time); + notify_button_released(touchpad, time); +} + +static void +process_fsm_events(struct touchpad_dispatch *touchpad, uint32_t time) +{ + uint32_t timeout = UINT32_MAX; + enum fsm_event *pevent; + enum fsm_event event; + + if (!touchpad->fsm.enable) + return; + + if (touchpad->fsm.events.size == 0) + return; + + wl_array_for_each(pevent, &touchpad->fsm.events) { + event = *pevent; + timeout = 0; + + switch (touchpad->fsm.state) { + case FSM_IDLE: + switch (event) { + case FSM_EVENT_TOUCH: + touchpad->fsm.state = FSM_TOUCH; + break; + default: + break; + } + break; + case FSM_TOUCH: + switch (event) { + case FSM_EVENT_RELEASE: + timeout = DEFAULT_TOUCHPAD_SINGLE_TAP_TIMEOUT; + touchpad->fsm.state = FSM_TAP; + break; + default: + touchpad->fsm.state = FSM_IDLE; + break; + } + break; + case FSM_TAP: + switch (event) { + case FSM_EVENT_TIMEOUT: + notify_tap(touchpad, time); + touchpad->fsm.state = FSM_IDLE; + break; + case FSM_EVENT_TOUCH: + notify_button_pressed(touchpad, time); + touchpad->fsm.state = FSM_TAP_2; + break; + default: + touchpad->fsm.state = FSM_IDLE; + break; + } + break; + case FSM_TAP_2: + switch (event) { + case FSM_EVENT_MOTION: + touchpad->fsm.state = FSM_DRAG; + break; + case FSM_EVENT_RELEASE: + notify_button_released(touchpad, time); + notify_tap(touchpad, time); + touchpad->fsm.state = FSM_IDLE; + break; + default: + touchpad->fsm.state = FSM_IDLE; + break; + } + break; + case FSM_DRAG: + switch (event) { + case FSM_EVENT_RELEASE: + notify_button_released(touchpad, time); + touchpad->fsm.state = FSM_IDLE; + break; + default: + touchpad->fsm.state = FSM_IDLE; + break; + } + break; + default: + weston_log("evdev-touchpad: Unknown state %d", + touchpad->fsm.state); + touchpad->fsm.state = FSM_IDLE; + break; + } + } + + if (timeout != UINT32_MAX) + wl_event_source_timer_update(touchpad->fsm.timer_source, + timeout); + + wl_array_release(&touchpad->fsm.events); + wl_array_init(&touchpad->fsm.events); +} + +static void +push_fsm_event(struct touchpad_dispatch *touchpad, + enum fsm_event event) +{ + enum fsm_event *pevent; + + if (!touchpad->fsm.enable) + return; + + pevent = wl_array_add(&touchpad->fsm.events, sizeof event); + if (pevent) + *pevent = event; + else + touchpad->fsm.state = FSM_IDLE; +} + +static int +fsm_timout_handler(void *data) +{ + struct touchpad_dispatch *touchpad = data; + + if (touchpad->fsm.events.size == 0) { + push_fsm_event(touchpad, FSM_EVENT_TIMEOUT); + process_fsm_events(touchpad, weston_compositor_get_time()); + } + + return 1; +} + +static void +touchpad_update_state(struct touchpad_dispatch *touchpad, uint32_t time) +{ + int motion_index; + int center_x, center_y; + double dx = 0.0, dy = 0.0; + + if (touchpad->reset || + touchpad->last_finger_state != touchpad->finger_state) { + touchpad->reset = 0; + touchpad->motion_count = 0; + touchpad->event_mask = TOUCHPAD_EVENT_NONE; + touchpad->event_mask_filter = + TOUCHPAD_EVENT_ABSOLUTE_X | TOUCHPAD_EVENT_ABSOLUTE_Y; + + touchpad->last_finger_state = touchpad->finger_state; + + process_fsm_events(touchpad, time); + + return; + } + touchpad->last_finger_state = touchpad->finger_state; + + if (!(touchpad->event_mask & TOUCHPAD_EVENT_REPORT)) + return; + else + touchpad->event_mask &= ~TOUCHPAD_EVENT_REPORT; + + if ((touchpad->event_mask & touchpad->event_mask_filter) != + touchpad->event_mask_filter) + return; + + touchpad->event_mask_filter = TOUCHPAD_EVENT_ABSOLUTE_ANY; + touchpad->event_mask = 0; + + /* Avoid noice by moving center only when delta reaches a threshold + * distance from the old center. */ + if (touchpad->motion_count > 0) { + center_x = hysteresis(touchpad->hw_abs.x, + touchpad->hysteresis.center_x, + touchpad->hysteresis.margin_x); + center_y = hysteresis(touchpad->hw_abs.y, + touchpad->hysteresis.center_y, + touchpad->hysteresis.margin_y); + } else { + center_x = touchpad->hw_abs.x; + center_y = touchpad->hw_abs.y; + } + touchpad->hysteresis.center_x = center_x; + touchpad->hysteresis.center_y = center_y; + touchpad->hw_abs.x = center_x; + touchpad->hw_abs.y = center_y; + + /* Update motion history tracker */ + motion_index = (touchpad->motion_index + 1) % TOUCHPAD_HISTORY_LENGTH; + touchpad->motion_index = motion_index; + touchpad->motion_history[motion_index].x = touchpad->hw_abs.x; + touchpad->motion_history[motion_index].y = touchpad->hw_abs.y; + if (touchpad->motion_count < 4) + touchpad->motion_count++; + + if (touchpad->motion_count >= 4) { + touchpad_get_delta(touchpad, &dx, &dy); + + filter_motion(touchpad, &dx, &dy, time); + + if (touchpad->finger_state == TOUCHPAD_FINGERS_ONE) { + notify_motion(touchpad->device->seat, time, + wl_fixed_from_double(dx), + wl_fixed_from_double(dy)); + } else if (touchpad->finger_state == TOUCHPAD_FINGERS_TWO) { + if (dx != 0.0) + notify_axis(touchpad->device->seat, + time, + WL_POINTER_AXIS_HORIZONTAL_SCROLL, + wl_fixed_from_double(dx)); + if (dy != 0.0) + notify_axis(touchpad->device->seat, + time, + WL_POINTER_AXIS_VERTICAL_SCROLL, + wl_fixed_from_double(dy)); + } + } + + if (!(touchpad->state & TOUCHPAD_STATE_MOVE) && + ((int)dx || (int)dy)) { + touchpad->state |= TOUCHPAD_STATE_MOVE; + push_fsm_event(touchpad, FSM_EVENT_MOTION); + } + + process_fsm_events(touchpad, time); +} + +static void +on_touch(struct touchpad_dispatch *touchpad) +{ + touchpad->state |= TOUCHPAD_STATE_TOUCH; + + push_fsm_event(touchpad, FSM_EVENT_TOUCH); +} + +static void +on_release(struct touchpad_dispatch *touchpad) +{ + + touchpad->reset = 1; + touchpad->state &= ~(TOUCHPAD_STATE_MOVE | TOUCHPAD_STATE_TOUCH); + + push_fsm_event(touchpad, FSM_EVENT_RELEASE); +} + +static inline void +process_absolute(struct touchpad_dispatch *touchpad, + struct evdev_device *device, + struct input_event *e) +{ + switch (e->code) { + case ABS_PRESSURE: + if (e->value > touchpad->pressure.touch_high && + !(touchpad->state & TOUCHPAD_STATE_TOUCH)) + on_touch(touchpad); + else if (e->value < touchpad->pressure.touch_low && + touchpad->state & TOUCHPAD_STATE_TOUCH) + on_release(touchpad); + + break; + case ABS_X: + if (touchpad->state & TOUCHPAD_STATE_TOUCH) { + touchpad->hw_abs.x = e->value; + touchpad->event_mask |= TOUCHPAD_EVENT_ABSOLUTE_ANY; + touchpad->event_mask |= TOUCHPAD_EVENT_ABSOLUTE_X; + } + break; + case ABS_Y: + if (touchpad->state & TOUCHPAD_STATE_TOUCH) { + touchpad->hw_abs.y = e->value; + touchpad->event_mask |= TOUCHPAD_EVENT_ABSOLUTE_ANY; + touchpad->event_mask |= TOUCHPAD_EVENT_ABSOLUTE_Y; + } + break; + } +} + +static inline void +process_key(struct touchpad_dispatch *touchpad, + struct evdev_device *device, + struct input_event *e, + uint32_t time) +{ + uint32_t code; + + switch (e->code) { + case BTN_TOUCH: + if (!touchpad->has_pressure) { + if (e->value && !(touchpad->state & TOUCHPAD_STATE_TOUCH)) + on_touch(touchpad); + else if (!e->value) + on_release(touchpad); + } + break; + case BTN_LEFT: + case BTN_RIGHT: + case BTN_MIDDLE: + case BTN_SIDE: + case BTN_EXTRA: + case BTN_FORWARD: + case BTN_BACK: + case BTN_TASK: + if (!touchpad->fsm.enable && e->code == BTN_LEFT && + touchpad->finger_state == TOUCHPAD_FINGERS_TWO) + code = BTN_RIGHT; + else + code = e->code; + notify_button(device->seat, time, code, + e->value ? WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED); + break; + case BTN_TOOL_PEN: + case BTN_TOOL_RUBBER: + case BTN_TOOL_BRUSH: + case BTN_TOOL_PENCIL: + case BTN_TOOL_AIRBRUSH: + case BTN_TOOL_MOUSE: + case BTN_TOOL_LENS: + touchpad->reset = 1; + break; + case BTN_TOOL_FINGER: + if (e->value) + touchpad->finger_state |= TOUCHPAD_FINGERS_ONE; + else + touchpad->finger_state &= ~TOUCHPAD_FINGERS_ONE; + break; + case BTN_TOOL_DOUBLETAP: + if (e->value) + touchpad->finger_state |= TOUCHPAD_FINGERS_TWO; + else + touchpad->finger_state &= ~TOUCHPAD_FINGERS_TWO; + break; + case BTN_TOOL_TRIPLETAP: + if (e->value) + touchpad->finger_state |= TOUCHPAD_FINGERS_THREE; + else + touchpad->finger_state &= ~TOUCHPAD_FINGERS_THREE; + break; + } +} + +static void +touchpad_process(struct evdev_dispatch *dispatch, + struct evdev_device *device, + struct input_event *e, + uint32_t time) +{ + struct touchpad_dispatch *touchpad = + (struct touchpad_dispatch *) dispatch; + + switch (e->type) { + case EV_SYN: + if (e->code == SYN_REPORT) + touchpad->event_mask |= TOUCHPAD_EVENT_REPORT; + break; + case EV_ABS: + process_absolute(touchpad, device, e); + break; + case EV_KEY: + process_key(touchpad, device, e, time); + break; + } + + touchpad_update_state(touchpad, time); +} + +static void +touchpad_destroy(struct evdev_dispatch *dispatch) +{ + struct touchpad_dispatch *touchpad = + (struct touchpad_dispatch *) dispatch; + + touchpad->filter->interface->destroy(touchpad->filter); + wl_event_source_remove(touchpad->fsm.timer_source); + free(dispatch); +} + +struct evdev_dispatch_interface touchpad_interface = { + touchpad_process, + touchpad_destroy +}; + +static void +touchpad_parse_config(struct touchpad_dispatch *touchpad, double diagonal) +{ + struct weston_compositor *compositor = + touchpad->device->seat->compositor; + struct weston_config_section *s; + double constant_accel_factor; + double min_accel_factor; + double max_accel_factor; + + s = weston_config_get_section(compositor->config, + "touchpad", NULL, NULL); + weston_config_section_get_double(s, "constant_accel_factor", + &constant_accel_factor, + DEFAULT_CONSTANT_ACCEL_NUMERATOR); + weston_config_section_get_double(s, "min_accel_factor", + &min_accel_factor, + DEFAULT_MIN_ACCEL_FACTOR); + weston_config_section_get_double(s, "max_accel_factor", + &max_accel_factor, + DEFAULT_MAX_ACCEL_FACTOR); + + touchpad->constant_accel_factor = + constant_accel_factor / diagonal; + touchpad->min_accel_factor = min_accel_factor; + touchpad->max_accel_factor = max_accel_factor; +} + +static int +touchpad_init(struct touchpad_dispatch *touchpad, + struct evdev_device *device) +{ + struct weston_motion_filter *accel; + struct wl_event_loop *loop; + + unsigned long prop_bits[INPUT_PROP_MAX]; + struct input_absinfo absinfo; + unsigned long abs_bits[NBITS(ABS_MAX)]; + + bool has_buttonpad; + + double width; + double height; + double diagonal; + + touchpad->base.interface = &touchpad_interface; + touchpad->device = device; + + /* Detect model */ + touchpad->model = get_touchpad_model(device); + + ioctl(device->fd, EVIOCGPROP(sizeof(prop_bits)), prop_bits); + has_buttonpad = TEST_BIT(prop_bits, INPUT_PROP_BUTTONPAD); + + /* Configure pressure */ + ioctl(device->fd, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), abs_bits); + if (TEST_BIT(abs_bits, ABS_PRESSURE)) { + ioctl(device->fd, EVIOCGABS(ABS_PRESSURE), &absinfo); + configure_touchpad_pressure(touchpad, + absinfo.minimum, + absinfo.maximum); + } + + /* Configure acceleration factor */ + width = abs(device->abs.max_x - device->abs.min_x); + height = abs(device->abs.max_y - device->abs.min_y); + diagonal = sqrt(width*width + height*height); + + touchpad_parse_config(touchpad, diagonal); + + touchpad->hysteresis.margin_x = + diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR; + touchpad->hysteresis.margin_y = + diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR; + touchpad->hysteresis.center_x = 0; + touchpad->hysteresis.center_y = 0; + + /* Configure acceleration profile */ + accel = create_pointer_accelator_filter(touchpad_profile); + if (accel == NULL) + return -1; + touchpad->filter = accel; + + /* Setup initial state */ + touchpad->reset = 1; + + memset(touchpad->motion_history, 0, sizeof touchpad->motion_history); + touchpad->motion_index = 0; + touchpad->motion_count = 0; + + touchpad->state = TOUCHPAD_STATE_NONE; + touchpad->last_finger_state = 0; + touchpad->finger_state = 0; + + wl_array_init(&touchpad->fsm.events); + touchpad->fsm.state = FSM_IDLE; + + loop = wl_display_get_event_loop(device->seat->compositor->wl_display); + touchpad->fsm.timer_source = + wl_event_loop_add_timer(loop, fsm_timout_handler, touchpad); + if (touchpad->fsm.timer_source == NULL) { + accel->interface->destroy(accel); + return -1; + } + + /* Configure */ + touchpad->fsm.enable = !has_buttonpad; + + return 0; +} + +struct evdev_dispatch * +evdev_touchpad_create(struct evdev_device *device) +{ + struct touchpad_dispatch *touchpad; + + touchpad = malloc(sizeof *touchpad); + if (touchpad == NULL) + return NULL; + + if (touchpad_init(touchpad, device) != 0) { + free(touchpad); + return NULL; + } + + return &touchpad->base; +} diff --git a/src/evdev.c b/src/evdev.c new file mode 100644 index 00000000..eb7631a7 --- /dev/null +++ b/src/evdev.c @@ -0,0 +1,719 @@ +/* + * Copyright © 2010 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compositor.h" +#include "evdev.h" + +#define DEFAULT_AXIS_STEP_DISTANCE wl_fixed_from_int(10) + +void +evdev_led_update(struct evdev_device *device, enum weston_led leds) +{ + static const struct { + enum weston_led weston; + int evdev; + } map[] = { + { LED_NUM_LOCK, LED_NUML }, + { LED_CAPS_LOCK, LED_CAPSL }, + { LED_SCROLL_LOCK, LED_SCROLLL }, + }; + struct input_event ev[ARRAY_LENGTH(map) + 1]; + unsigned int i; + + if (!device->caps & EVDEV_KEYBOARD) + return; + + memset(ev, 0, sizeof(ev)); + for (i = 0; i < ARRAY_LENGTH(map); i++) { + ev[i].type = EV_LED; + ev[i].code = map[i].evdev; + ev[i].value = !!(leds & map[i].weston); + } + ev[i].type = EV_SYN; + ev[i].code = SYN_REPORT; + + i = write(device->fd, ev, sizeof ev); + (void)i; /* no, we really don't care about the return value */ +} + +static void +transform_absolute(struct evdev_device *device, int32_t *x, int32_t *y) +{ + if (!device->abs.apply_calibration) { + *x = device->abs.x; + *y = device->abs.y; + return; + } else { + *x = device->abs.x * device->abs.calibration[0] + + device->abs.y * device->abs.calibration[1] + + device->abs.calibration[2]; + + *y = device->abs.x * device->abs.calibration[3] + + device->abs.y * device->abs.calibration[4] + + device->abs.calibration[5]; + } +} + +static void +evdev_flush_pending_event(struct evdev_device *device, uint32_t time) +{ + struct weston_seat *master = device->seat; + wl_fixed_t x, y; + int32_t cx, cy; + int slot; + + slot = device->mt.slot; + + switch (device->pending_event) { + case EVDEV_NONE: + return; + case EVDEV_RELATIVE_MOTION: + notify_motion(master, time, device->rel.dx, device->rel.dy); + device->rel.dx = 0; + device->rel.dy = 0; + goto handled; + case EVDEV_ABSOLUTE_MT_DOWN: + weston_output_transform_coordinate(device->output, + device->mt.slots[slot].x, + device->mt.slots[slot].y, + &x, &y); + notify_touch(master, time, + slot, x, y, WL_TOUCH_DOWN); + goto handled; + case EVDEV_ABSOLUTE_MT_MOTION: + weston_output_transform_coordinate(device->output, + device->mt.slots[slot].x, + device->mt.slots[slot].y, + &x, &y); + notify_touch(master, time, + slot, x, y, WL_TOUCH_MOTION); + goto handled; + case EVDEV_ABSOLUTE_MT_UP: + notify_touch(master, time, slot, 0, 0, + WL_TOUCH_UP); + goto handled; + case EVDEV_ABSOLUTE_TOUCH_DOWN: + transform_absolute(device, &cx, &cy); + weston_output_transform_coordinate(device->output, + cx, cy, &x, &y); + notify_touch(master, time, 0, x, y, WL_TOUCH_DOWN); + goto handled; + case EVDEV_ABSOLUTE_MOTION: + transform_absolute(device, &cx, &cy); + weston_output_transform_coordinate(device->output, + cx, cy, &x, &y); + + if (device->caps & EVDEV_TOUCH) + notify_touch(master, time, 0, x, y, WL_TOUCH_MOTION); + else + notify_motion_absolute(master, time, x, y); + goto handled; + case EVDEV_ABSOLUTE_TOUCH_UP: + notify_touch(master, time, 0, 0, 0, WL_TOUCH_UP); + goto handled; + } + + assert(0 && "Unknown pending event type"); + +handled: + device->pending_event = EVDEV_NONE; +} + +static void +evdev_process_touch_button(struct evdev_device *device, int time, int value) +{ + if (device->pending_event != EVDEV_NONE && + device->pending_event != EVDEV_ABSOLUTE_MOTION) + evdev_flush_pending_event(device, time); + + device->pending_event = (value ? + EVDEV_ABSOLUTE_TOUCH_DOWN : + EVDEV_ABSOLUTE_TOUCH_UP); +} + +static inline void +evdev_process_key(struct evdev_device *device, struct input_event *e, int time) +{ + /* ignore kernel key repeat */ + if (e->value == 2) + return; + + if (e->code == BTN_TOUCH) { + if (!device->is_mt) + evdev_process_touch_button(device, time, e->value); + return; + } + + evdev_flush_pending_event(device, time); + + switch (e->code) { + case BTN_LEFT: + case BTN_RIGHT: + case BTN_MIDDLE: + case BTN_SIDE: + case BTN_EXTRA: + case BTN_FORWARD: + case BTN_BACK: + case BTN_TASK: + notify_button(device->seat, + time, e->code, + e->value ? WL_POINTER_BUTTON_STATE_PRESSED : + WL_POINTER_BUTTON_STATE_RELEASED); + break; + + default: + notify_key(device->seat, + time, e->code, + e->value ? WL_KEYBOARD_KEY_STATE_PRESSED : + WL_KEYBOARD_KEY_STATE_RELEASED, + STATE_UPDATE_AUTOMATIC); + break; + } +} + +static void +evdev_process_touch(struct evdev_device *device, + struct input_event *e, + uint32_t time) +{ + const int screen_width = device->output->current_mode->width; + const int screen_height = device->output->current_mode->height; + + switch (e->code) { + case ABS_MT_SLOT: + evdev_flush_pending_event(device, time); + device->mt.slot = e->value; + break; + case ABS_MT_TRACKING_ID: + if (device->pending_event != EVDEV_NONE && + device->pending_event != EVDEV_ABSOLUTE_MT_MOTION) + evdev_flush_pending_event(device, time); + if (e->value >= 0) + device->pending_event = EVDEV_ABSOLUTE_MT_DOWN; + else + device->pending_event = EVDEV_ABSOLUTE_MT_UP; + break; + case ABS_MT_POSITION_X: + device->mt.slots[device->mt.slot].x = + (e->value - device->abs.min_x) * screen_width / + (device->abs.max_x - device->abs.min_x); + if (device->pending_event == EVDEV_NONE) + device->pending_event = EVDEV_ABSOLUTE_MT_MOTION; + break; + case ABS_MT_POSITION_Y: + device->mt.slots[device->mt.slot].y = + (e->value - device->abs.min_y) * screen_height / + (device->abs.max_y - device->abs.min_y); + if (device->pending_event == EVDEV_NONE) + device->pending_event = EVDEV_ABSOLUTE_MT_MOTION; + break; + } +} + +static inline void +evdev_process_absolute_motion(struct evdev_device *device, + struct input_event *e) +{ + const int screen_width = device->output->current_mode->width; + const int screen_height = device->output->current_mode->height; + + switch (e->code) { + case ABS_X: + device->abs.x = + (e->value - device->abs.min_x) * screen_width / + (device->abs.max_x - device->abs.min_x); + if (device->pending_event == EVDEV_NONE) + device->pending_event = EVDEV_ABSOLUTE_MOTION; + break; + case ABS_Y: + device->abs.y = + (e->value - device->abs.min_y) * screen_height / + (device->abs.max_y - device->abs.min_y); + if (device->pending_event == EVDEV_NONE) + device->pending_event = EVDEV_ABSOLUTE_MOTION; + break; + } +} + +static inline void +evdev_process_relative(struct evdev_device *device, + struct input_event *e, uint32_t time) +{ + switch (e->code) { + case REL_X: + if (device->pending_event != EVDEV_RELATIVE_MOTION) + evdev_flush_pending_event(device, time); + device->rel.dx += wl_fixed_from_int(e->value); + device->pending_event = EVDEV_RELATIVE_MOTION; + break; + case REL_Y: + if (device->pending_event != EVDEV_RELATIVE_MOTION) + evdev_flush_pending_event(device, time); + device->rel.dy += wl_fixed_from_int(e->value); + device->pending_event = EVDEV_RELATIVE_MOTION; + break; + case REL_WHEEL: + evdev_flush_pending_event(device, time); + switch (e->value) { + case -1: + /* Scroll down */ + case 1: + /* Scroll up */ + notify_axis(device->seat, + time, + WL_POINTER_AXIS_VERTICAL_SCROLL, + -1 * e->value * DEFAULT_AXIS_STEP_DISTANCE); + break; + default: + break; + } + break; + case REL_HWHEEL: + evdev_flush_pending_event(device, time); + switch (e->value) { + case -1: + /* Scroll left */ + case 1: + /* Scroll right */ + notify_axis(device->seat, + time, + WL_POINTER_AXIS_HORIZONTAL_SCROLL, + e->value * DEFAULT_AXIS_STEP_DISTANCE); + break; + default: + break; + + } + } +} + +static inline void +evdev_process_absolute(struct evdev_device *device, + struct input_event *e, + uint32_t time) +{ + if (device->is_mt) { + evdev_process_touch(device, e, time); + } else { + evdev_process_absolute_motion(device, e); + } +} + +static void +fallback_process(struct evdev_dispatch *dispatch, + struct evdev_device *device, + struct input_event *event, + uint32_t time) +{ + switch (event->type) { + case EV_REL: + evdev_process_relative(device, event, time); + break; + case EV_ABS: + evdev_process_absolute(device, event, time); + break; + case EV_KEY: + evdev_process_key(device, event, time); + break; + case EV_SYN: + evdev_flush_pending_event(device, time); + break; + } +} + +static void +fallback_destroy(struct evdev_dispatch *dispatch) +{ + free(dispatch); +} + +struct evdev_dispatch_interface fallback_interface = { + fallback_process, + fallback_destroy +}; + +static struct evdev_dispatch * +fallback_dispatch_create(void) +{ + struct evdev_dispatch *dispatch = malloc(sizeof *dispatch); + if (dispatch == NULL) + return NULL; + + dispatch->interface = &fallback_interface; + + return dispatch; +} + +static void +evdev_process_events(struct evdev_device *device, + struct input_event *ev, int count) +{ + struct evdev_dispatch *dispatch = device->dispatch; + struct input_event *e, *end; + uint32_t time = 0; + + e = ev; + end = e + count; + for (e = ev; e < end; e++) { + time = e->time.tv_sec * 1000 + e->time.tv_usec / 1000; + + dispatch->interface->process(dispatch, device, e, time); + } +} + +static int +evdev_device_data(int fd, uint32_t mask, void *data) +{ + struct weston_compositor *ec; + struct evdev_device *device = data; + struct input_event ev[32]; + int len; + + ec = device->seat->compositor; + if (!ec->focus) + return 1; + + /* If the compositor is repainting, this function is called only once + * per frame and we have to process all the events available on the + * fd, otherwise there will be input lag. */ + do { + if (device->mtdev) + len = mtdev_get(device->mtdev, fd, ev, + ARRAY_LENGTH(ev)) * + sizeof (struct input_event); + else + len = read(fd, &ev, sizeof ev); + + if (len < 0 || len % sizeof ev[0] != 0) { + if (len < 0 && errno != EAGAIN && errno != EINTR) { + weston_log("device %s died\n", + device->devnode); + wl_event_source_remove(device->source); + device->source = NULL; + } + + return 1; + } + + evdev_process_events(device, ev, len / sizeof ev[0]); + + } while (len > 0); + + return 1; +} + +static int +evdev_handle_device(struct evdev_device *device) +{ + struct input_absinfo absinfo; + unsigned long ev_bits[NBITS(EV_MAX)]; + unsigned long abs_bits[NBITS(ABS_MAX)]; + unsigned long rel_bits[NBITS(REL_MAX)]; + unsigned long key_bits[NBITS(KEY_MAX)]; + int has_key, has_abs; + unsigned int i; + + has_key = 0; + has_abs = 0; + device->caps = 0; + + ioctl(device->fd, EVIOCGBIT(0, sizeof(ev_bits)), ev_bits); + if (TEST_BIT(ev_bits, EV_ABS)) { + has_abs = 1; + + ioctl(device->fd, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), + abs_bits); + + if (TEST_BIT(abs_bits, ABS_WHEEL) || + TEST_BIT(abs_bits, ABS_GAS) || + TEST_BIT(abs_bits, ABS_BRAKE) || + TEST_BIT(abs_bits, ABS_HAT0X)) { + weston_log("device %s is a joystick, ignoring\n", + device->devnode); + return 0; + } + + if (TEST_BIT(abs_bits, ABS_X)) { + ioctl(device->fd, EVIOCGABS(ABS_X), &absinfo); + device->abs.min_x = absinfo.minimum; + device->abs.max_x = absinfo.maximum; + device->caps |= EVDEV_MOTION_ABS; + } + if (TEST_BIT(abs_bits, ABS_Y)) { + ioctl(device->fd, EVIOCGABS(ABS_Y), &absinfo); + device->abs.min_y = absinfo.minimum; + device->abs.max_y = absinfo.maximum; + device->caps |= EVDEV_MOTION_ABS; + } + /* We only handle the slotted Protocol B in weston. + Devices with ABS_MT_POSITION_* but not ABS_MT_SLOT + require mtdev for conversion. */ + if (TEST_BIT(abs_bits, ABS_MT_POSITION_X) && + TEST_BIT(abs_bits, ABS_MT_POSITION_Y)) { + ioctl(device->fd, EVIOCGABS(ABS_MT_POSITION_X), + &absinfo); + device->abs.min_x = absinfo.minimum; + device->abs.max_x = absinfo.maximum; + ioctl(device->fd, EVIOCGABS(ABS_MT_POSITION_Y), + &absinfo); + device->abs.min_y = absinfo.minimum; + device->abs.max_y = absinfo.maximum; + device->is_mt = 1; + device->caps |= EVDEV_TOUCH; + + if (!TEST_BIT(abs_bits, ABS_MT_SLOT)) { + device->mtdev = mtdev_new_open(device->fd); + if (!device->mtdev) { + weston_log("mtdev required but failed to open for %s\n", + device->devnode); + return 0; + } + device->mt.slot = device->mtdev->caps.slot.value; + } else { + ioctl(device->fd, EVIOCGABS(ABS_MT_SLOT), + &absinfo); + device->mt.slot = absinfo.value; + } + } + } + if (TEST_BIT(ev_bits, EV_REL)) { + ioctl(device->fd, EVIOCGBIT(EV_REL, sizeof(rel_bits)), + rel_bits); + if (TEST_BIT(rel_bits, REL_X) || TEST_BIT(rel_bits, REL_Y)) + device->caps |= EVDEV_MOTION_REL; + } + if (TEST_BIT(ev_bits, EV_KEY)) { + has_key = 1; + ioctl(device->fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), + key_bits); + if (TEST_BIT(key_bits, BTN_TOOL_FINGER) && + !TEST_BIT(key_bits, BTN_TOOL_PEN) && + has_abs) { + device->dispatch = evdev_touchpad_create(device); + weston_log("input device %s, %s is a touchpad\n", + device->devname, device->devnode); + } + for (i = KEY_ESC; i < KEY_MAX; i++) { + if (i >= BTN_MISC && i < KEY_OK) + continue; + if (TEST_BIT(key_bits, i)) { + device->caps |= EVDEV_KEYBOARD; + break; + } + } + if (TEST_BIT(key_bits, BTN_TOUCH)) { + device->caps |= EVDEV_TOUCH; + } + for (i = BTN_MISC; i < BTN_JOYSTICK; i++) { + if (TEST_BIT(key_bits, i)) { + device->caps |= EVDEV_BUTTON; + device->caps &= ~EVDEV_TOUCH; + break; + } + } + } + if (TEST_BIT(ev_bits, EV_LED)) { + device->caps |= EVDEV_KEYBOARD; + } + + /* This rule tries to catch accelerometer devices and opt out. We may + * want to adjust the protocol later adding a proper event for dealing + * with accelerometers and implement here accordingly */ + if (has_abs && !has_key && !device->is_mt) { + weston_log("input device %s, %s " + "ignored: unsupported device type\n", + device->devname, device->devnode); + return 0; + } + + return 1; +} + +static int +evdev_configure_device(struct evdev_device *device) +{ + if ((device->caps & (EVDEV_MOTION_ABS | EVDEV_MOTION_REL)) && + (device->caps & EVDEV_BUTTON)) { + weston_seat_init_pointer(device->seat); + device->seat_caps |= EVDEV_SEAT_POINTER; + weston_log("input device %s, %s is a pointer caps =%s%s%s\n", + device->devname, device->devnode, + device->caps & EVDEV_MOTION_ABS ? " absolute-motion" : "", + device->caps & EVDEV_MOTION_REL ? " relative-motion": "", + device->caps & EVDEV_BUTTON ? " button" : ""); + } + if ((device->caps & EVDEV_KEYBOARD)) { + if (weston_seat_init_keyboard(device->seat, NULL) < 0) + return -1; + device->seat_caps |= EVDEV_SEAT_KEYBOARD; + weston_log("input device %s, %s is a keyboard\n", + device->devname, device->devnode); + } + if ((device->caps & EVDEV_TOUCH)) { + weston_seat_init_touch(device->seat); + device->seat_caps |= EVDEV_SEAT_TOUCH; + weston_log("input device %s, %s is a touch device\n", + device->devname, device->devnode); + } + + return 0; +} + +struct evdev_device * +evdev_device_create(struct weston_seat *seat, const char *path, int device_fd) +{ + struct evdev_device *device; + struct weston_compositor *ec; + char devname[256] = "unknown"; + + device = zalloc(sizeof *device); + if (device == NULL) + return NULL; + + ec = seat->compositor; + device->output = + container_of(ec->output_list.next, struct weston_output, link); + + device->seat = seat; + device->seat_caps = 0; + device->is_mt = 0; + device->mtdev = NULL; + device->devnode = strdup(path); + device->mt.slot = -1; + device->rel.dx = 0; + device->rel.dy = 0; + device->dispatch = NULL; + device->fd = device_fd; + device->pending_event = EVDEV_NONE; + wl_list_init(&device->link); + + ioctl(device->fd, EVIOCGNAME(sizeof(devname)), devname); + devname[sizeof(devname) - 1] = '\0'; + device->devname = strdup(devname); + + if (!evdev_handle_device(device)) { + evdev_device_destroy(device); + return EVDEV_UNHANDLED_DEVICE; + } + + if (evdev_configure_device(device) == -1) + goto err; + + /* If the dispatch was not set up use the fallback. */ + if (device->dispatch == NULL) + device->dispatch = fallback_dispatch_create(); + if (device->dispatch == NULL) + goto err; + + device->source = wl_event_loop_add_fd(ec->input_loop, device->fd, + WL_EVENT_READABLE, + evdev_device_data, device); + if (device->source == NULL) + goto err; + + return device; + +err: + evdev_device_destroy(device); + return NULL; +} + +void +evdev_device_destroy(struct evdev_device *device) +{ + struct evdev_dispatch *dispatch; + + if (device->seat_caps & EVDEV_SEAT_POINTER) + weston_seat_release_pointer(device->seat); + if (device->seat_caps & EVDEV_SEAT_KEYBOARD) + weston_seat_release_keyboard(device->seat); + if (device->seat_caps & EVDEV_SEAT_TOUCH) + weston_seat_release_touch(device->seat); + + dispatch = device->dispatch; + if (dispatch) + dispatch->interface->destroy(dispatch); + + if (device->source) + wl_event_source_remove(device->source); + wl_list_remove(&device->link); + if (device->mtdev) + mtdev_close_delete(device->mtdev); + close(device->fd); + free(device->devname); + free(device->devnode); + free(device); +} + +void +evdev_notify_keyboard_focus(struct weston_seat *seat, + struct wl_list *evdev_devices) +{ + struct evdev_device *device; + struct wl_array keys; + unsigned int i, set; + char evdev_keys[(KEY_CNT + 7) / 8]; + char all_keys[(KEY_CNT + 7) / 8]; + uint32_t *k; + int ret; + + if (!seat->keyboard) + return; + + memset(all_keys, 0, sizeof all_keys); + wl_list_for_each(device, evdev_devices, link) { + memset(evdev_keys, 0, sizeof evdev_keys); + ret = ioctl(device->fd, + EVIOCGKEY(sizeof evdev_keys), evdev_keys); + if (ret < 0) { + weston_log("failed to get keys for device %s\n", + device->devnode); + continue; + } + for (i = 0; i < ARRAY_LENGTH(evdev_keys); i++) + all_keys[i] |= evdev_keys[i]; + } + + wl_array_init(&keys); + for (i = 0; i < KEY_CNT; i++) { + set = all_keys[i >> 3] & (1 << (i & 7)); + if (set) { + k = wl_array_add(&keys, sizeof *k); + *k = i; + } + } + + notify_keyboard_focus_in(seat, &keys, STATE_UPDATE_AUTOMATIC); + + wl_array_release(&keys); +} diff --git a/src/evdev.h b/src/evdev.h new file mode 100644 index 00000000..e146d1a4 --- /dev/null +++ b/src/evdev.h @@ -0,0 +1,139 @@ +/* + * Copyright © 2011, 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef EVDEV_H +#define EVDEV_H + +#include "config.h" + +#include +#include + +#define MAX_SLOTS 16 + +enum evdev_event_type { + EVDEV_NONE, + EVDEV_ABSOLUTE_TOUCH_DOWN, + EVDEV_ABSOLUTE_MOTION, + EVDEV_ABSOLUTE_TOUCH_UP, + EVDEV_ABSOLUTE_MT_DOWN, + EVDEV_ABSOLUTE_MT_MOTION, + EVDEV_ABSOLUTE_MT_UP, + EVDEV_RELATIVE_MOTION, +}; + +enum evdev_device_capability { + EVDEV_KEYBOARD = (1 << 0), + EVDEV_BUTTON = (1 << 1), + EVDEV_MOTION_ABS = (1 << 2), + EVDEV_MOTION_REL = (1 << 3), + EVDEV_TOUCH = (1 << 4), +}; + +enum evdev_device_seat_capability { + EVDEV_SEAT_POINTER = (1 << 0), + EVDEV_SEAT_KEYBOARD = (1 << 1), + EVDEV_SEAT_TOUCH = (1 << 2) +}; + +struct evdev_device { + struct weston_seat *seat; + struct wl_list link; + struct wl_event_source *source; + struct weston_output *output; + struct evdev_dispatch *dispatch; + char *devnode; + char *devname; + int fd; + struct { + int min_x, max_x, min_y, max_y; + int32_t x, y; + + int apply_calibration; + float calibration[6]; + } abs; + + struct { + int slot; + struct { + int32_t x, y; + } slots[MAX_SLOTS]; + } mt; + struct mtdev *mtdev; + + struct { + wl_fixed_t dx, dy; + } rel; + + enum evdev_event_type pending_event; + enum evdev_device_capability caps; + enum evdev_device_seat_capability seat_caps; + + int is_mt; +}; + +/* copied from udev/extras/input_id/input_id.c */ +/* we must use this kernel-compatible implementation */ +#define BITS_PER_LONG (sizeof(unsigned long) * 8) +#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) +#define OFF(x) ((x)%BITS_PER_LONG) +#define BIT(x) (1UL<> OFF(bit)) & 1) +/* end copied */ + +#define EVDEV_UNHANDLED_DEVICE ((struct evdev_device *) 1) + +struct evdev_dispatch; + +struct evdev_dispatch_interface { + /* Process an evdev input event. */ + void (*process)(struct evdev_dispatch *dispatch, + struct evdev_device *device, + struct input_event *event, + uint32_t time); + + /* Destroy an event dispatch handler and free all its resources. */ + void (*destroy)(struct evdev_dispatch *dispatch); +}; + +struct evdev_dispatch { + struct evdev_dispatch_interface *interface; +}; + +struct evdev_dispatch * +evdev_touchpad_create(struct evdev_device *device); + +void +evdev_led_update(struct evdev_device *device, enum weston_led leds); + +struct evdev_device * +evdev_device_create(struct weston_seat *seat, const char *path, int device_fd); + +void +evdev_device_destroy(struct evdev_device *device); + +void +evdev_notify_keyboard_focus(struct weston_seat *seat, + struct wl_list *evdev_devices); + +#endif /* EVDEV_H */ diff --git a/src/filter.c b/src/filter.c new file mode 100644 index 00000000..a55ebf27 --- /dev/null +++ b/src/filter.c @@ -0,0 +1,338 @@ +/* + * Copyright © 2012 Jonas Ådahl + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include + +#include "compositor.h" +#include "filter.h" + +void +weston_filter_dispatch(struct weston_motion_filter *filter, + struct weston_motion_params *motion, + void *data, uint32_t time) +{ + filter->interface->filter(filter, motion, data, time); +} + +/* + * Pointer acceleration filter + */ + +#define MAX_VELOCITY_DIFF 1.0 +#define MOTION_TIMEOUT 300 /* (ms) */ +#define NUM_POINTER_TRACKERS 16 + +struct pointer_tracker { + double dx; + double dy; + uint32_t time; + int dir; +}; + +struct pointer_accelerator; +struct pointer_accelerator { + struct weston_motion_filter base; + + accel_profile_func_t profile; + + double velocity; + double last_velocity; + int last_dx; + int last_dy; + + struct pointer_tracker *trackers; + int cur_tracker; +}; + +enum directions { + N = 1 << 0, + NE = 1 << 1, + E = 1 << 2, + SE = 1 << 3, + S = 1 << 4, + SW = 1 << 5, + W = 1 << 6, + NW = 1 << 7, + UNDEFINED_DIRECTION = 0xff +}; + +static int +get_direction(int dx, int dy) +{ + int dir = UNDEFINED_DIRECTION; + int d1, d2; + double r; + + if (abs(dx) < 2 && abs(dy) < 2) { + if (dx > 0 && dy > 0) + dir = S | SE | E; + else if (dx > 0 && dy < 0) + dir = N | NE | E; + else if (dx < 0 && dy > 0) + dir = S | SW | W; + else if (dx < 0 && dy < 0) + dir = N | NW | W; + else if (dx > 0) + dir = NW | W | SW; + else if (dx < 0) + dir = NE | E | SE; + else if (dy > 0) + dir = SE | S | SW; + else if (dy < 0) + dir = NE | N | NW; + } + else { + /* Calculate r within the interval [0 to 8) + * + * r = [0 .. 2π] where 0 is North + * d_f = r / 2π ([0 .. 1)) + * d_8 = 8 * d_f + */ + r = atan2(dy, dx); + r = fmod(r + 2.5*M_PI, 2*M_PI); + r *= 4*M_1_PI; + + /* Mark one or two close enough octants */ + d1 = (int)(r + 0.9) % 8; + d2 = (int)(r + 0.1) % 8; + + dir = (1 << d1) | (1 << d2); + } + + return dir; +} + +static void +feed_trackers(struct pointer_accelerator *accel, + double dx, double dy, + uint32_t time) +{ + int i, current; + struct pointer_tracker *trackers = accel->trackers; + + for (i = 0; i < NUM_POINTER_TRACKERS; i++) { + trackers[i].dx += dx; + trackers[i].dy += dy; + } + + current = (accel->cur_tracker + 1) % NUM_POINTER_TRACKERS; + accel->cur_tracker = current; + + trackers[current].dx = 0.0; + trackers[current].dy = 0.0; + trackers[current].time = time; + trackers[current].dir = get_direction(dx, dy); +} + +static struct pointer_tracker * +tracker_by_offset(struct pointer_accelerator *accel, unsigned int offset) +{ + unsigned int index = + (accel->cur_tracker + NUM_POINTER_TRACKERS - offset) + % NUM_POINTER_TRACKERS; + return &accel->trackers[index]; +} + +static double +calculate_tracker_velocity(struct pointer_tracker *tracker, uint32_t time) +{ + int dx; + int dy; + double distance; + + dx = tracker->dx; + dy = tracker->dy; + distance = sqrt(dx*dx + dy*dy); + return distance / (double)(time - tracker->time); +} + +static double +calculate_velocity(struct pointer_accelerator *accel, uint32_t time) +{ + struct pointer_tracker *tracker; + double velocity; + double result = 0.0; + double initial_velocity; + double velocity_diff; + unsigned int offset; + + unsigned int dir = tracker_by_offset(accel, 0)->dir; + + /* Find first velocity */ + for (offset = 1; offset < NUM_POINTER_TRACKERS; offset++) { + tracker = tracker_by_offset(accel, offset); + + if (time <= tracker->time) + continue; + + result = initial_velocity = + calculate_tracker_velocity(tracker, time); + if (initial_velocity > 0.0) + break; + } + + /* Find least recent vector within a timelimit, maximum velocity diff + * and direction threshold. */ + for (; offset < NUM_POINTER_TRACKERS; offset++) { + tracker = tracker_by_offset(accel, offset); + + /* Stop if too far away in time */ + if (time - tracker->time > MOTION_TIMEOUT || + tracker->time > time) + break; + + /* Stop if direction changed */ + dir &= tracker->dir; + if (dir == 0) + break; + + velocity = calculate_tracker_velocity(tracker, time); + + /* Stop if velocity differs too much from initial */ + velocity_diff = fabs(initial_velocity - velocity); + if (velocity_diff > MAX_VELOCITY_DIFF) + break; + + result = velocity; + } + + return result; +} + +static double +acceleration_profile(struct pointer_accelerator *accel, + void *data, double velocity, uint32_t time) +{ + return accel->profile(&accel->base, data, velocity, time); +} + +static double +calculate_acceleration(struct pointer_accelerator *accel, + void *data, double velocity, uint32_t time) +{ + double factor; + + /* Use Simpson's rule to calculate the avarage acceleration between + * the previous motion and the most recent. */ + factor = acceleration_profile(accel, data, velocity, time); + factor += acceleration_profile(accel, data, accel->last_velocity, time); + factor += 4.0 * + acceleration_profile(accel, data, + (accel->last_velocity + velocity) / 2, + time); + + factor = factor / 6.0; + + return factor; +} + +static double +soften_delta(double last_delta, double delta) +{ + if (delta < -1.0 || delta > 1.0) { + if (delta > last_delta) + return delta - 0.5; + else if (delta < last_delta) + return delta + 0.5; + } + + return delta; +} + +static void +apply_softening(struct pointer_accelerator *accel, + struct weston_motion_params *motion) +{ + motion->dx = soften_delta(accel->last_dx, motion->dx); + motion->dy = soften_delta(accel->last_dy, motion->dy); +} + +static void +accelerator_filter(struct weston_motion_filter *filter, + struct weston_motion_params *motion, + void *data, uint32_t time) +{ + struct pointer_accelerator *accel = + (struct pointer_accelerator *) filter; + double velocity; + double accel_value; + + feed_trackers(accel, motion->dx, motion->dy, time); + velocity = calculate_velocity(accel, time); + accel_value = calculate_acceleration(accel, data, velocity, time); + + motion->dx = accel_value * motion->dx; + motion->dy = accel_value * motion->dy; + + apply_softening(accel, motion); + + accel->last_dx = motion->dx; + accel->last_dy = motion->dy; + + accel->last_velocity = velocity; +} + +static void +accelerator_destroy(struct weston_motion_filter *filter) +{ + struct pointer_accelerator *accel = + (struct pointer_accelerator *) filter; + + free(accel->trackers); + free(accel); +} + +struct weston_motion_filter_interface accelerator_interface = { + accelerator_filter, + accelerator_destroy +}; + +struct weston_motion_filter * +create_pointer_accelator_filter(accel_profile_func_t profile) +{ + struct pointer_accelerator *filter; + + filter = malloc(sizeof *filter); + if (filter == NULL) + return NULL; + + filter->base.interface = &accelerator_interface; + wl_list_init(&filter->base.link); + + filter->profile = profile; + filter->last_velocity = 0.0; + filter->last_dx = 0; + filter->last_dy = 0; + + filter->trackers = + calloc(NUM_POINTER_TRACKERS, sizeof *filter->trackers); + filter->cur_tracker = 0; + + return &filter->base; +} diff --git a/src/filter.h b/src/filter.h new file mode 100644 index 00000000..850ce8df --- /dev/null +++ b/src/filter.h @@ -0,0 +1,67 @@ +/* + * Copyright © 2012 Jonas Ådahl + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _FILTER_H_ +#define _FILTER_H_ + +#include "config.h" + +#include + +#include "compositor.h" + +struct weston_motion_params { + double dx, dy; +}; + +struct weston_motion_filter; + +WL_EXPORT void +weston_filter_dispatch(struct weston_motion_filter *filter, + struct weston_motion_params *motion, + void *data, uint32_t time); + + +struct weston_motion_filter_interface { + void (*filter)(struct weston_motion_filter *filter, + struct weston_motion_params *motion, + void *data, uint32_t time); + void (*destroy)(struct weston_motion_filter *filter); +}; + +struct weston_motion_filter { + struct weston_motion_filter_interface *interface; + struct wl_list link; +}; + +WL_EXPORT struct weston_motion_filter * +create_linear_acceleration_filter(double speed); + +typedef double (*accel_profile_func_t)(struct weston_motion_filter *filter, + void *data, + double velocity, + uint32_t time); + +WL_EXPORT struct weston_motion_filter * +create_pointer_accelator_filter(accel_profile_func_t filter); + +#endif // _FILTER_H_ diff --git a/src/gl-renderer.c b/src/gl-renderer.c new file mode 100644 index 00000000..ae69f220 --- /dev/null +++ b/src/gl-renderer.c @@ -0,0 +1,1865 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "gl-renderer.h" +#include "vertex-clipping.h" + +#include +#include "weston-egl-ext.h" + +struct gl_shader { + GLuint program; + GLuint vertex_shader, fragment_shader; + GLint proj_uniform; + GLint tex_uniforms[3]; + GLint alpha_uniform; + GLint color_uniform; + const char *vertex_source, *fragment_source; +}; + +#define BUFFER_DAMAGE_COUNT 2 + +struct gl_output_state { + EGLSurface egl_surface; + pixman_region32_t buffer_damage[BUFFER_DAMAGE_COUNT]; +}; + +enum buffer_type { + BUFFER_TYPE_NULL, + BUFFER_TYPE_SHM, + BUFFER_TYPE_EGL +}; + +struct gl_surface_state { + GLfloat color[4]; + struct gl_shader *shader; + + GLuint textures[3]; + int num_textures; + int needs_full_upload; + pixman_region32_t texture_damage; + + EGLImageKHR images[3]; + GLenum target; + int num_images; + + struct weston_buffer_reference buffer_ref; + enum buffer_type buffer_type; + int pitch; /* in pixels */ + int height; /* in pixels */ + int y_inverted; +}; + +struct gl_renderer { + struct weston_renderer base; + int fragment_shader_debug; + int fan_debug; + + EGLDisplay egl_display; + EGLContext egl_context; + EGLConfig egl_config; + + struct { + int32_t top, bottom, left, right; + GLuint texture; + int32_t width, height; + } border; + + struct wl_array vertices; + struct wl_array indices; /* only used in compositor-wayland */ + struct wl_array vtxcnt; + + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d; + PFNEGLCREATEIMAGEKHRPROC create_image; + PFNEGLDESTROYIMAGEKHRPROC destroy_image; + + int has_unpack_subimage; + + PFNEGLBINDWAYLANDDISPLAYWL bind_display; + PFNEGLUNBINDWAYLANDDISPLAYWL unbind_display; + PFNEGLQUERYWAYLANDBUFFERWL query_buffer; + int has_bind_display; + + int has_egl_image_external; + + int has_egl_buffer_age; + + struct gl_shader texture_shader_rgba; + struct gl_shader texture_shader_rgbx; + struct gl_shader texture_shader_egl_external; + struct gl_shader texture_shader_y_uv; + struct gl_shader texture_shader_y_u_v; + struct gl_shader texture_shader_y_xuxv; + struct gl_shader invert_color_shader; + struct gl_shader solid_shader; + struct gl_shader *current_shader; +}; + +static inline struct gl_output_state * +get_output_state(struct weston_output *output) +{ + return (struct gl_output_state *)output->renderer_state; +} + +static inline struct gl_surface_state * +get_surface_state(struct weston_surface *surface) +{ + return (struct gl_surface_state *)surface->renderer_state; +} + +static inline struct gl_renderer * +get_renderer(struct weston_compositor *ec) +{ + return (struct gl_renderer *)ec->renderer; +} + +static const char * +egl_error_string(EGLint code) +{ +#define MYERRCODE(x) case x: return #x; + switch (code) { + MYERRCODE(EGL_SUCCESS) + MYERRCODE(EGL_NOT_INITIALIZED) + MYERRCODE(EGL_BAD_ACCESS) + MYERRCODE(EGL_BAD_ALLOC) + MYERRCODE(EGL_BAD_ATTRIBUTE) + MYERRCODE(EGL_BAD_CONTEXT) + MYERRCODE(EGL_BAD_CONFIG) + MYERRCODE(EGL_BAD_CURRENT_SURFACE) + MYERRCODE(EGL_BAD_DISPLAY) + MYERRCODE(EGL_BAD_SURFACE) + MYERRCODE(EGL_BAD_MATCH) + MYERRCODE(EGL_BAD_PARAMETER) + MYERRCODE(EGL_BAD_NATIVE_PIXMAP) + MYERRCODE(EGL_BAD_NATIVE_WINDOW) + MYERRCODE(EGL_CONTEXT_LOST) + default: + return "unknown"; + } +#undef MYERRCODE +} + +WL_EXPORT void +gl_renderer_print_egl_error_state(void) +{ + EGLint code; + + code = eglGetError(); + weston_log("EGL error state: %s (0x%04lx)\n", + egl_error_string(code), (long)code); +} + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) > (b)) ? (b) : (a)) + +/* + * Compute the boundary vertices of the intersection of the global coordinate + * aligned rectangle 'rect', and an arbitrary quadrilateral produced from + * 'surf_rect' when transformed from surface coordinates into global coordinates. + * The vertices are written to 'ex' and 'ey', and the return value is the + * number of vertices. Vertices are produced in clockwise winding order. + * Guarantees to produce either zero vertices, or 3-8 vertices with non-zero + * polygon area. + */ +static int +calculate_edges(struct weston_surface *es, pixman_box32_t *rect, + pixman_box32_t *surf_rect, GLfloat *ex, GLfloat *ey) +{ + + struct clip_context ctx; + int i, n; + GLfloat min_x, max_x, min_y, max_y; + struct polygon8 surf = { + { surf_rect->x1, surf_rect->x2, surf_rect->x2, surf_rect->x1 }, + { surf_rect->y1, surf_rect->y1, surf_rect->y2, surf_rect->y2 }, + 4 + }; + + ctx.clip.x1 = rect->x1; + ctx.clip.y1 = rect->y1; + ctx.clip.x2 = rect->x2; + ctx.clip.y2 = rect->y2; + + /* transform surface to screen space: */ + for (i = 0; i < surf.n; i++) + weston_surface_to_global_float(es, surf.x[i], surf.y[i], + &surf.x[i], &surf.y[i]); + + /* find bounding box: */ + min_x = max_x = surf.x[0]; + min_y = max_y = surf.y[0]; + + for (i = 1; i < surf.n; i++) { + min_x = min(min_x, surf.x[i]); + max_x = max(max_x, surf.x[i]); + min_y = min(min_y, surf.y[i]); + max_y = max(max_y, surf.y[i]); + } + + /* First, simple bounding box check to discard early transformed + * surface rects that do not intersect with the clip region: + */ + if ((min_x >= ctx.clip.x2) || (max_x <= ctx.clip.x1) || + (min_y >= ctx.clip.y2) || (max_y <= ctx.clip.y1)) + return 0; + + /* Simple case, bounding box edges are parallel to surface edges, + * there will be only four edges. We just need to clip the surface + * vertices to the clip rect bounds: + */ + if (!es->transform.enabled) { + return clip_simple(&ctx, &surf, ex, ey); + } + + /* Transformed case: use a general polygon clipping algorithm to + * clip the surface rectangle with each side of 'rect'. + * The algorithm is Sutherland-Hodgman, as explained in + * http://www.codeguru.com/cpp/misc/misc/graphics/article.php/c8965/Polygon-Clipping.htm + * but without looking at any of that code. + */ + n = clip_transformed(&ctx, &surf, ex, ey); + + if (n < 3) + return 0; + + return n; +} + +static int +texture_region(struct weston_surface *es, pixman_region32_t *region, + pixman_region32_t *surf_region) +{ + struct gl_surface_state *gs = get_surface_state(es); + struct weston_compositor *ec = es->compositor; + struct gl_renderer *gr = get_renderer(ec); + GLfloat *v, inv_width, inv_height; + unsigned int *vtxcnt, nvtx = 0; + pixman_box32_t *rects, *surf_rects; + int i, j, k, nrects, nsurf; + + rects = pixman_region32_rectangles(region, &nrects); + surf_rects = pixman_region32_rectangles(surf_region, &nsurf); + + /* worst case we can have 8 vertices per rect (ie. clipped into + * an octagon): + */ + v = wl_array_add(&gr->vertices, nrects * nsurf * 8 * 4 * sizeof *v); + vtxcnt = wl_array_add(&gr->vtxcnt, nrects * nsurf * sizeof *vtxcnt); + + inv_width = 1.0 / gs->pitch; + inv_height = 1.0 / gs->height; + + for (i = 0; i < nrects; i++) { + pixman_box32_t *rect = &rects[i]; + for (j = 0; j < nsurf; j++) { + pixman_box32_t *surf_rect = &surf_rects[j]; + GLfloat sx, sy, bx, by; + GLfloat ex[8], ey[8]; /* edge points in screen space */ + int n; + + /* The transformed surface, after clipping to the clip region, + * can have as many as eight sides, emitted as a triangle-fan. + * The first vertex in the triangle fan can be chosen arbitrarily, + * since the area is guaranteed to be convex. + * + * If a corner of the transformed surface falls outside of the + * clip region, instead of emitting one vertex for the corner + * of the surface, up to two are emitted for two corresponding + * intersection point(s) between the surface and the clip region. + * + * To do this, we first calculate the (up to eight) points that + * form the intersection of the clip rect and the transformed + * surface. + */ + n = calculate_edges(es, rect, surf_rect, ex, ey); + if (n < 3) + continue; + + /* emit edge points: */ + for (k = 0; k < n; k++) { + weston_surface_from_global_float(es, ex[k], ey[k], &sx, &sy); + /* position: */ + *(v++) = ex[k]; + *(v++) = ey[k]; + /* texcoord: */ + weston_surface_to_buffer_float(es, sx, sy, + &bx, &by); + *(v++) = bx * inv_width; + if (gs->y_inverted) { + *(v++) = by * inv_height; + } else { + *(v++) = (gs->height - by) * inv_height; + } + } + + vtxcnt[nvtx++] = n; + } + } + + return nvtx; +} + +static void +triangle_fan_debug(struct weston_surface *surface, int first, int count) +{ + struct weston_compositor *compositor = surface->compositor; + struct gl_renderer *gr = get_renderer(compositor); + int i; + GLushort *buffer; + GLushort *index; + int nelems; + static int color_idx = 0; + static const GLfloat color[][4] = { + { 1.0, 0.0, 0.0, 1.0 }, + { 0.0, 1.0, 0.0, 1.0 }, + { 0.0, 0.0, 1.0, 1.0 }, + { 1.0, 1.0, 1.0, 1.0 }, + }; + + nelems = (count - 1 + count - 2) * 2; + + buffer = malloc(sizeof(GLushort) * nelems); + index = buffer; + + for (i = 1; i < count; i++) { + *index++ = first; + *index++ = first + i; + } + + for (i = 2; i < count; i++) { + *index++ = first + i - 1; + *index++ = first + i; + } + + glUseProgram(gr->solid_shader.program); + glUniform4fv(gr->solid_shader.color_uniform, 1, + color[color_idx++ % ARRAY_LENGTH(color)]); + glDrawElements(GL_LINES, nelems, GL_UNSIGNED_SHORT, buffer); + glUseProgram(gr->current_shader->program); + free(buffer); +} + +static void +repaint_region(struct weston_surface *es, pixman_region32_t *region, + pixman_region32_t *surf_region) +{ + struct weston_compositor *ec = es->compositor; + struct gl_renderer *gr = get_renderer(ec); + GLfloat *v; + unsigned int *vtxcnt; + int i, first, nfans; + + /* The final region to be painted is the intersection of + * 'region' and 'surf_region'. However, 'region' is in the global + * coordinates, and 'surf_region' is in the surface-local + * coordinates. texture_region() will iterate over all pairs of + * rectangles from both regions, compute the intersection + * polygon for each pair, and store it as a triangle fan if + * it has a non-zero area (at least 3 vertices1, actually). + */ + nfans = texture_region(es, region, surf_region); + + v = gr->vertices.data; + vtxcnt = gr->vtxcnt.data; + + /* position: */ + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof *v, &v[0]); + glEnableVertexAttribArray(0); + + /* texcoord: */ + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof *v, &v[2]); + glEnableVertexAttribArray(1); + + for (i = 0, first = 0; i < nfans; i++) { + glDrawArrays(GL_TRIANGLE_FAN, first, vtxcnt[i]); + if (gr->fan_debug) + triangle_fan_debug(es, first, vtxcnt[i]); + first += vtxcnt[i]; + } + + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(0); + + gr->vertices.size = 0; + gr->vtxcnt.size = 0; +} + +static int +use_output(struct weston_output *output) +{ + static int errored; + struct gl_output_state *go = get_output_state(output); + struct gl_renderer *gr = get_renderer(output->compositor); + EGLBoolean ret; + + ret = eglMakeCurrent(gr->egl_display, go->egl_surface, + go->egl_surface, gr->egl_context); + + if (ret == EGL_FALSE) { + if (errored) + return -1; + errored = 1; + weston_log("Failed to make EGL context current.\n"); + gl_renderer_print_egl_error_state(); + return -1; + } + + return 0; +} + +static int +shader_init(struct gl_shader *shader, struct gl_renderer *gr, + const char *vertex_source, const char *fragment_source); + +static void +use_shader(struct gl_renderer *gr, struct gl_shader *shader) +{ + if (!shader->program) { + int ret; + + ret = shader_init(shader, gr, + shader->vertex_source, + shader->fragment_source); + + if (ret < 0) + weston_log("warning: failed to compile shader\n"); + } + + if (gr->current_shader == shader) + return; + glUseProgram(shader->program); + gr->current_shader = shader; +} + +static void +shader_uniforms(struct gl_shader *shader, + struct weston_surface *surface, + struct weston_output *output) +{ + int i; + struct gl_surface_state *gs = get_surface_state(surface); + + glUniformMatrix4fv(shader->proj_uniform, + 1, GL_FALSE, output->matrix.d); + glUniform4fv(shader->color_uniform, 1, gs->color); + glUniform1f(shader->alpha_uniform, surface->alpha); + + for (i = 0; i < gs->num_textures; i++) + glUniform1i(shader->tex_uniforms[i], i); +} + +static void +draw_surface(struct weston_surface *es, struct weston_output *output, + pixman_region32_t *damage) /* in global coordinates */ +{ + struct weston_compositor *ec = es->compositor; + struct gl_renderer *gr = get_renderer(ec); + struct gl_surface_state *gs = get_surface_state(es); + /* repaint bounding region in global coordinates: */ + pixman_region32_t repaint; + /* non-opaque region in surface coordinates: */ + pixman_region32_t surface_blend; + GLint filter; + int i; + + pixman_region32_init(&repaint); + pixman_region32_intersect(&repaint, + &es->transform.boundingbox, damage); + pixman_region32_subtract(&repaint, &repaint, &es->clip); + + if (!pixman_region32_not_empty(&repaint)) + goto out; + + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + if (gr->fan_debug) { + use_shader(gr, &gr->solid_shader); + shader_uniforms(&gr->solid_shader, es, output); + } + + use_shader(gr, gs->shader); + shader_uniforms(gs->shader, es, output); + + if (es->transform.enabled || output->zoom.active || output->current_scale != es->buffer_scale) + filter = GL_LINEAR; + else + filter = GL_NEAREST; + + for (i = 0; i < gs->num_textures; i++) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(gs->target, gs->textures[i]); + glTexParameteri(gs->target, GL_TEXTURE_MIN_FILTER, filter); + glTexParameteri(gs->target, GL_TEXTURE_MAG_FILTER, filter); + } + + /* blended region is whole surface minus opaque region: */ + pixman_region32_init_rect(&surface_blend, 0, 0, + es->geometry.width, es->geometry.height); + pixman_region32_subtract(&surface_blend, &surface_blend, &es->opaque); + + if (pixman_region32_not_empty(&es->opaque)) { + if (gs->shader == &gr->texture_shader_rgba) { + /* Special case for RGBA textures with possibly + * bad data in alpha channel: use the shader + * that forces texture alpha = 1.0. + * Xwayland surfaces need this. + */ + use_shader(gr, &gr->texture_shader_rgbx); + shader_uniforms(&gr->texture_shader_rgbx, es, output); + } + + if (es->alpha < 1.0) + glEnable(GL_BLEND); + else + glDisable(GL_BLEND); + + repaint_region(es, &repaint, &es->opaque); + } + + if (pixman_region32_not_empty(&surface_blend)) { + use_shader(gr, gs->shader); + glEnable(GL_BLEND); + repaint_region(es, &repaint, &surface_blend); + } + + pixman_region32_fini(&surface_blend); + +out: + pixman_region32_fini(&repaint); +} + +static void +repaint_surfaces(struct weston_output *output, pixman_region32_t *damage) +{ + struct weston_compositor *compositor = output->compositor; + struct weston_surface *surface; + + wl_list_for_each_reverse(surface, &compositor->surface_list, link) + if (surface->plane == &compositor->primary_plane) + draw_surface(surface, output, damage); +} + + +static int +texture_border(struct weston_output *output) +{ + struct weston_compositor *ec = output->compositor; + struct gl_renderer *gr = get_renderer(ec); + GLfloat *d; + unsigned short *p; + int i, j, k, n; + GLfloat x[4], y[4], u[4], v[4]; + + x[0] = -gr->border.left; + x[1] = 0; + x[2] = output->current_mode->width; + x[3] = output->current_mode->width + gr->border.right; + + y[0] = -gr->border.top; + y[1] = 0; + y[2] = output->current_mode->height; + y[3] = output->current_mode->height + gr->border.bottom; + + u[0] = 0.0; + u[1] = (GLfloat) gr->border.left / gr->border.width; + u[2] = (GLfloat) (gr->border.width - gr->border.right) / gr->border.width; + u[3] = 1.0; + + v[0] = 0.0; + v[1] = (GLfloat) gr->border.top / gr->border.height; + v[2] = (GLfloat) (gr->border.height - gr->border.bottom) / gr->border.height; + v[3] = 1.0; + + n = 8; + d = wl_array_add(&gr->vertices, n * 16 * sizeof *d); + p = wl_array_add(&gr->indices, n * 6 * sizeof *p); + + k = 0; + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) { + + if (i == 1 && j == 1) + continue; + + d[ 0] = x[i]; + d[ 1] = y[j]; + d[ 2] = u[i]; + d[ 3] = v[j]; + + d[ 4] = x[i]; + d[ 5] = y[j + 1]; + d[ 6] = u[i]; + d[ 7] = v[j + 1]; + + d[ 8] = x[i + 1]; + d[ 9] = y[j]; + d[10] = u[i + 1]; + d[11] = v[j]; + + d[12] = x[i + 1]; + d[13] = y[j + 1]; + d[14] = u[i + 1]; + d[15] = v[j + 1]; + + p[0] = k + 0; + p[1] = k + 1; + p[2] = k + 2; + p[3] = k + 2; + p[4] = k + 1; + p[5] = k + 3; + + d += 16; + p += 6; + k += 4; + } + + return k / 4; +} + +static void +draw_border(struct weston_output *output) +{ + struct weston_compositor *ec = output->compositor; + struct gl_renderer *gr = get_renderer(ec); + struct gl_shader *shader = &gr->texture_shader_rgba; + GLfloat *v; + int n; + + glDisable(GL_BLEND); + use_shader(gr, shader); + + glUniformMatrix4fv(shader->proj_uniform, + 1, GL_FALSE, output->matrix.d); + + glUniform1i(shader->tex_uniforms[0], 0); + glUniform1f(shader->alpha_uniform, 1); + + n = texture_border(output); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, gr->border.texture); + + v = gr->vertices.data; + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof *v, &v[0]); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof *v, &v[2]); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + + glDrawElements(GL_TRIANGLES, n * 6, + GL_UNSIGNED_SHORT, gr->indices.data); + + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(0); + + gr->vertices.size = 0; + gr->indices.size = 0; +} + +static void +output_get_buffer_damage(struct weston_output *output, + pixman_region32_t *buffer_damage) +{ + struct gl_output_state *go = get_output_state(output); + struct gl_renderer *gr = get_renderer(output->compositor); + EGLint buffer_age = 0; + EGLBoolean ret; + int i; + + if (gr->has_egl_buffer_age) { + ret = eglQuerySurface(gr->egl_display, go->egl_surface, + EGL_BUFFER_AGE_EXT, &buffer_age); + if (ret == EGL_FALSE) { + weston_log("buffer age query failed.\n"); + gl_renderer_print_egl_error_state(); + } + } + + if (buffer_age == 0 || buffer_age - 1 > BUFFER_DAMAGE_COUNT) + pixman_region32_copy(buffer_damage, &output->region); + else + for (i = 0; i < buffer_age - 1; i++) + pixman_region32_union(buffer_damage, buffer_damage, + &go->buffer_damage[i]); +} + +static void +output_rotate_damage(struct weston_output *output, + pixman_region32_t *output_damage) +{ + struct gl_output_state *go = get_output_state(output); + struct gl_renderer *gr = get_renderer(output->compositor); + int i; + + if (!gr->has_egl_buffer_age) + return; + + for (i = BUFFER_DAMAGE_COUNT - 1; i >= 1; i--) + pixman_region32_copy(&go->buffer_damage[i], + &go->buffer_damage[i - 1]); + + pixman_region32_copy(&go->buffer_damage[0], output_damage); +} + +static void +gl_renderer_repaint_output(struct weston_output *output, + pixman_region32_t *output_damage) +{ + struct gl_output_state *go = get_output_state(output); + struct weston_compositor *compositor = output->compositor; + struct gl_renderer *gr = get_renderer(compositor); + EGLBoolean ret; + static int errored; + int32_t width, height; + pixman_region32_t buffer_damage, total_damage; + + width = output->current_mode->width + + output->border.left + output->border.right; + height = output->current_mode->height + + output->border.top + output->border.bottom; + + glViewport(0, 0, width, height); + + if (use_output(output) < 0) + return; + + /* if debugging, redraw everything outside the damage to clean up + * debug lines from the previous draw on this buffer: + */ + if (gr->fan_debug) { + pixman_region32_t undamaged; + pixman_region32_init(&undamaged); + pixman_region32_subtract(&undamaged, &output->region, + output_damage); + gr->fan_debug = 0; + repaint_surfaces(output, &undamaged); + gr->fan_debug = 1; + pixman_region32_fini(&undamaged); + } + + pixman_region32_init(&total_damage); + pixman_region32_init(&buffer_damage); + + output_get_buffer_damage(output, &buffer_damage); + output_rotate_damage(output, output_damage); + + pixman_region32_union(&total_damage, &buffer_damage, output_damage); + + repaint_surfaces(output, &total_damage); + + pixman_region32_fini(&total_damage); + pixman_region32_fini(&buffer_damage); + + if (gr->border.texture) + draw_border(output); + + pixman_region32_copy(&output->previous_damage, output_damage); + wl_signal_emit(&output->frame_signal, output); + + ret = eglSwapBuffers(gr->egl_display, go->egl_surface); + if (ret == EGL_FALSE && !errored) { + errored = 1; + weston_log("Failed in eglSwapBuffers.\n"); + gl_renderer_print_egl_error_state(); + } + +} + +static int +gl_renderer_read_pixels(struct weston_output *output, + pixman_format_code_t format, void *pixels, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height) +{ + GLenum gl_format; + + switch (format) { + case PIXMAN_a8r8g8b8: + gl_format = GL_BGRA_EXT; + break; + case PIXMAN_a8b8g8r8: + gl_format = GL_RGBA; + break; + default: + return -1; + } + + if (use_output(output) < 0) + return -1; + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(x, y, width, height, gl_format, + GL_UNSIGNED_BYTE, pixels); + + return 0; +} + +static void +gl_renderer_flush_damage(struct weston_surface *surface) +{ + struct gl_renderer *gr = get_renderer(surface->compositor); + struct gl_surface_state *gs = get_surface_state(surface); + struct weston_buffer *buffer = gs->buffer_ref.buffer; + GLenum format; + int pixel_type; + +#ifdef GL_EXT_unpack_subimage + pixman_box32_t *rectangles; + void *data; + int i, n; +#endif + + pixman_region32_union(&gs->texture_damage, + &gs->texture_damage, &surface->damage); + + if (!buffer) + return; + + /* Avoid upload, if the texture won't be used this time. + * We still accumulate the damage in texture_damage, and + * hold the reference to the buffer, in case the surface + * migrates back to the primary plane. + */ + if (surface->plane != &surface->compositor->primary_plane) + return; + + if (!pixman_region32_not_empty(&gs->texture_damage)) + goto done; + + switch (wl_shm_buffer_get_format(buffer->shm_buffer)) { + case WL_SHM_FORMAT_XRGB8888: + case WL_SHM_FORMAT_ARGB8888: + format = GL_BGRA_EXT; + pixel_type = GL_UNSIGNED_BYTE; + break; + case WL_SHM_FORMAT_RGB565: + format = GL_RGB; + pixel_type = GL_UNSIGNED_SHORT_5_6_5; + break; + default: + weston_log("warning: unknown shm buffer format\n"); + format = GL_BGRA_EXT; + pixel_type = GL_UNSIGNED_BYTE; + } + + glBindTexture(GL_TEXTURE_2D, gs->textures[0]); + + if (!gr->has_unpack_subimage) { + glTexImage2D(GL_TEXTURE_2D, 0, format, + gs->pitch, buffer->height, 0, + format, pixel_type, + wl_shm_buffer_get_data(buffer->shm_buffer)); + + goto done; + } + +#ifdef GL_EXT_unpack_subimage + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, gs->pitch); + data = wl_shm_buffer_get_data(buffer->shm_buffer); + + if (gs->needs_full_upload) { + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0); + glTexSubImage2D(GL_TEXTURE_2D, 0, + 0, 0, gs->pitch, buffer->height, + format, pixel_type, data); + goto done; + } + + rectangles = pixman_region32_rectangles(&gs->texture_damage, &n); + for (i = 0; i < n; i++) { + pixman_box32_t r; + + r = weston_surface_to_buffer_rect(surface, rectangles[i]); + + glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, r.x1); + glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, r.y1); + glTexSubImage2D(GL_TEXTURE_2D, 0, r.x1, r.y1, + r.x2 - r.x1, r.y2 - r.y1, + format, pixel_type, data); + } +#endif + +done: + pixman_region32_fini(&gs->texture_damage); + pixman_region32_init(&gs->texture_damage); + gs->needs_full_upload = 0; + + weston_buffer_reference(&gs->buffer_ref, NULL); +} + +static void +ensure_textures(struct gl_surface_state *gs, int num_textures) +{ + int i; + + if (num_textures <= gs->num_textures) + return; + + for (i = gs->num_textures; i < num_textures; i++) { + glGenTextures(1, &gs->textures[i]); + glBindTexture(gs->target, gs->textures[i]); + glTexParameteri(gs->target, + GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(gs->target, + GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + gs->num_textures = num_textures; + glBindTexture(gs->target, 0); +} + +static void +gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer, + struct wl_shm_buffer *shm_buffer) +{ + struct weston_compositor *ec = es->compositor; + struct gl_renderer *gr = get_renderer(ec); + struct gl_surface_state *gs = get_surface_state(es); + int pitch; + + buffer->shm_buffer = shm_buffer; + buffer->width = wl_shm_buffer_get_width(shm_buffer); + buffer->height = wl_shm_buffer_get_height(shm_buffer); + + switch (wl_shm_buffer_get_format(shm_buffer)) { + case WL_SHM_FORMAT_XRGB8888: + gs->shader = &gr->texture_shader_rgbx; + pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; + break; + case WL_SHM_FORMAT_ARGB8888: + gs->shader = &gr->texture_shader_rgba; + pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; + break; + case WL_SHM_FORMAT_RGB565: + gs->shader = &gr->texture_shader_rgbx; + pitch = wl_shm_buffer_get_stride(shm_buffer) / 2; + break; + default: + weston_log("warning: unknown shm buffer format\n"); + gs->shader = &gr->texture_shader_rgba; + pitch = wl_shm_buffer_get_stride(shm_buffer) / 4; + } + + /* Only allocate a texture if it doesn't match existing one. + * If a switch from DRM allocated buffer to a SHM buffer is + * happening, we need to allocate a new texture buffer. */ + if (pitch != gs->pitch || + buffer->height != gs->height || + gs->buffer_type != BUFFER_TYPE_SHM) { + gs->pitch = pitch; + gs->height = buffer->height; + gs->target = GL_TEXTURE_2D; + gs->buffer_type = BUFFER_TYPE_SHM; + gs->needs_full_upload = 1; + gs->y_inverted = 1; + + ensure_textures(gs, 1); + glBindTexture(GL_TEXTURE_2D, gs->textures[0]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_EXT, + gs->pitch, buffer->height, 0, + GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL); + } +} + +static void +gl_renderer_attach_egl(struct weston_surface *es, struct weston_buffer *buffer, + uint32_t format) +{ + struct weston_compositor *ec = es->compositor; + struct gl_renderer *gr = get_renderer(ec); + struct gl_surface_state *gs = get_surface_state(es); + EGLint attribs[3]; + int i, num_planes; + + buffer->legacy_buffer = (struct wl_buffer *)buffer->resource; + gr->query_buffer(gr->egl_display, buffer->legacy_buffer, + EGL_WIDTH, &buffer->width); + gr->query_buffer(gr->egl_display, buffer->legacy_buffer, + EGL_HEIGHT, &buffer->height); + gr->query_buffer(gr->egl_display, buffer->legacy_buffer, + EGL_WAYLAND_Y_INVERTED_WL, &buffer->y_inverted); + + for (i = 0; i < gs->num_images; i++) + gr->destroy_image(gr->egl_display, gs->images[i]); + gs->num_images = 0; + gs->target = GL_TEXTURE_2D; + switch (format) { + case EGL_TEXTURE_RGB: + case EGL_TEXTURE_RGBA: + default: + num_planes = 1; + gs->shader = &gr->texture_shader_rgba; + break; + case EGL_TEXTURE_EXTERNAL_WL: + num_planes = 1; + gs->target = GL_TEXTURE_EXTERNAL_OES; + gs->shader = &gr->texture_shader_egl_external; + break; + case EGL_TEXTURE_Y_UV_WL: + num_planes = 2; + gs->shader = &gr->texture_shader_y_uv; + break; + case EGL_TEXTURE_Y_U_V_WL: + num_planes = 3; + gs->shader = &gr->texture_shader_y_u_v; + break; + case EGL_TEXTURE_Y_XUXV_WL: + num_planes = 2; + gs->shader = &gr->texture_shader_y_xuxv; + break; + } + + ensure_textures(gs, num_planes); + for (i = 0; i < num_planes; i++) { + attribs[0] = EGL_WAYLAND_PLANE_WL; + attribs[1] = i; + attribs[2] = EGL_NONE; + gs->images[i] = gr->create_image(gr->egl_display, + NULL, + EGL_WAYLAND_BUFFER_WL, + buffer->legacy_buffer, + attribs); + if (!gs->images[i]) { + weston_log("failed to create img for plane %d\n", i); + continue; + } + gs->num_images++; + + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(gs->target, gs->textures[i]); + gr->image_target_texture_2d(gs->target, + gs->images[i]); + } + + gs->pitch = buffer->width; + gs->height = buffer->height; + gs->buffer_type = BUFFER_TYPE_EGL; + gs->y_inverted = buffer->y_inverted; +} + +static void +gl_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) +{ + struct weston_compositor *ec = es->compositor; + struct gl_renderer *gr = get_renderer(ec); + struct gl_surface_state *gs = get_surface_state(es); + struct wl_shm_buffer *shm_buffer; + EGLint format; + int i; + + weston_buffer_reference(&gs->buffer_ref, buffer); + + if (!buffer) { + for (i = 0; i < gs->num_images; i++) { + gr->destroy_image(gr->egl_display, gs->images[i]); + gs->images[i] = NULL; + } + gs->num_images = 0; + glDeleteTextures(gs->num_textures, gs->textures); + gs->num_textures = 0; + gs->buffer_type = BUFFER_TYPE_NULL; + gs->y_inverted = 1; + return; + } + + shm_buffer = wl_shm_buffer_get(buffer->resource); + + if (shm_buffer) + gl_renderer_attach_shm(es, buffer, shm_buffer); + else if (gr->query_buffer(gr->egl_display, (void *) buffer->resource, + EGL_TEXTURE_FORMAT, &format)) + gl_renderer_attach_egl(es, buffer, format); + else { + weston_log("unhandled buffer type!\n"); + weston_buffer_reference(&gs->buffer_ref, NULL); + gs->buffer_type = BUFFER_TYPE_NULL; + gs->y_inverted = 1; + } +} + +static void +gl_renderer_surface_set_color(struct weston_surface *surface, + float red, float green, float blue, float alpha) +{ + struct gl_surface_state *gs = get_surface_state(surface); + struct gl_renderer *gr = get_renderer(surface->compositor); + + gs->color[0] = red; + gs->color[1] = green; + gs->color[2] = blue; + gs->color[3] = alpha; + + gs->shader = &gr->solid_shader; +} + +static int +gl_renderer_create_surface(struct weston_surface *surface) +{ + struct gl_surface_state *gs; + + gs = calloc(1, sizeof *gs); + if (!gs) + return -1; + + /* A buffer is never attached to solid color surfaces, yet + * they still go through texcoord computations. Do not divide + * by zero there. + */ + gs->pitch = 1; + gs->y_inverted = 1; + + pixman_region32_init(&gs->texture_damage); + surface->renderer_state = gs; + + return 0; +} + +static void +gl_renderer_destroy_surface(struct weston_surface *surface) +{ + struct gl_surface_state *gs = get_surface_state(surface); + struct gl_renderer *gr = get_renderer(surface->compositor); + int i; + + glDeleteTextures(gs->num_textures, gs->textures); + + for (i = 0; i < gs->num_images; i++) + gr->destroy_image(gr->egl_display, gs->images[i]); + + weston_buffer_reference(&gs->buffer_ref, NULL); + pixman_region32_fini(&gs->texture_damage); + free(gs); +} + +static const char vertex_shader[] = + "uniform mat4 proj;\n" + "attribute vec2 position;\n" + "attribute vec2 texcoord;\n" + "varying vec2 v_texcoord;\n" + "void main()\n" + "{\n" + " gl_Position = proj * vec4(position, 0.0, 1.0);\n" + " v_texcoord = texcoord;\n" + "}\n"; + +/* Declare common fragment shader uniforms */ +#define FRAGMENT_CONVERT_YUV \ + " y *= alpha;\n" \ + " u *= alpha;\n" \ + " v *= alpha;\n" \ + " gl_FragColor.r = y + 1.59602678 * v;\n" \ + " gl_FragColor.g = y - 0.39176229 * u - 0.81296764 * v;\n" \ + " gl_FragColor.b = y + 2.01723214 * u;\n" \ + " gl_FragColor.a = alpha;\n" + +static const char fragment_debug[] = + " gl_FragColor = vec4(0.0, 0.3, 0.0, 0.2) + gl_FragColor * 0.8;\n"; + +static const char fragment_brace[] = + "}\n"; + +static const char texture_fragment_shader_rgba[] = + "precision mediump float;\n" + "varying vec2 v_texcoord;\n" + "uniform sampler2D tex;\n" + "uniform float alpha;\n" + "void main()\n" + "{\n" + " gl_FragColor = alpha * texture2D(tex, v_texcoord)\n;" + ; + +static const char texture_fragment_shader_rgbx[] = + "precision mediump float;\n" + "varying vec2 v_texcoord;\n" + "uniform sampler2D tex;\n" + "uniform float alpha;\n" + "void main()\n" + "{\n" + " gl_FragColor.rgb = alpha * texture2D(tex, v_texcoord).rgb\n;" + " gl_FragColor.a = alpha;\n" + ; + +static const char texture_fragment_shader_egl_external[] = + "#extension GL_OES_EGL_image_external : require\n" + "precision mediump float;\n" + "varying vec2 v_texcoord;\n" + "uniform samplerExternalOES tex;\n" + "uniform float alpha;\n" + "void main()\n" + "{\n" + " gl_FragColor = alpha * texture2D(tex, v_texcoord)\n;" + ; + +static const char texture_fragment_shader_y_uv[] = + "precision mediump float;\n" + "uniform sampler2D tex;\n" + "uniform sampler2D tex1;\n" + "varying vec2 v_texcoord;\n" + "uniform float alpha;\n" + "void main() {\n" + " float y = 1.16438356 * (texture2D(tex, v_texcoord).x - 0.0625);\n" + " float u = texture2D(tex1, v_texcoord).r - 0.5;\n" + " float v = texture2D(tex1, v_texcoord).g - 0.5;\n" + FRAGMENT_CONVERT_YUV + ; + +static const char texture_fragment_shader_y_u_v[] = + "precision mediump float;\n" + "uniform sampler2D tex;\n" + "uniform sampler2D tex1;\n" + "uniform sampler2D tex2;\n" + "varying vec2 v_texcoord;\n" + "uniform float alpha;\n" + "void main() {\n" + " float y = 1.16438356 * (texture2D(tex, v_texcoord).x - 0.0625);\n" + " float u = texture2D(tex1, v_texcoord).x - 0.5;\n" + " float v = texture2D(tex2, v_texcoord).x - 0.5;\n" + FRAGMENT_CONVERT_YUV + ; + +static const char texture_fragment_shader_y_xuxv[] = + "precision mediump float;\n" + "uniform sampler2D tex;\n" + "uniform sampler2D tex1;\n" + "varying vec2 v_texcoord;\n" + "uniform float alpha;\n" + "void main() {\n" + " float y = 1.16438356 * (texture2D(tex, v_texcoord).x - 0.0625);\n" + " float u = texture2D(tex1, v_texcoord).g - 0.5;\n" + " float v = texture2D(tex1, v_texcoord).a - 0.5;\n" + FRAGMENT_CONVERT_YUV + ; + +static const char solid_fragment_shader[] = + "precision mediump float;\n" + "uniform vec4 color;\n" + "uniform float alpha;\n" + "void main()\n" + "{\n" + " gl_FragColor = alpha * color\n;" + ; + +static int +compile_shader(GLenum type, int count, const char **sources) +{ + GLuint s; + char msg[512]; + GLint status; + + s = glCreateShader(type); + glShaderSource(s, count, sources, NULL); + glCompileShader(s); + glGetShaderiv(s, GL_COMPILE_STATUS, &status); + if (!status) { + glGetShaderInfoLog(s, sizeof msg, NULL, msg); + weston_log("shader info: %s\n", msg); + return GL_NONE; + } + + return s; +} + +static int +shader_init(struct gl_shader *shader, struct gl_renderer *renderer, + const char *vertex_source, const char *fragment_source) +{ + char msg[512]; + GLint status; + int count; + const char *sources[3]; + + shader->vertex_shader = + compile_shader(GL_VERTEX_SHADER, 1, &vertex_source); + + if (renderer->fragment_shader_debug) { + sources[0] = fragment_source; + sources[1] = fragment_debug; + sources[2] = fragment_brace; + count = 3; + } else { + sources[0] = fragment_source; + sources[1] = fragment_brace; + count = 2; + } + + shader->fragment_shader = + compile_shader(GL_FRAGMENT_SHADER, count, sources); + + shader->program = glCreateProgram(); + glAttachShader(shader->program, shader->vertex_shader); + glAttachShader(shader->program, shader->fragment_shader); + glBindAttribLocation(shader->program, 0, "position"); + glBindAttribLocation(shader->program, 1, "texcoord"); + + glLinkProgram(shader->program); + glGetProgramiv(shader->program, GL_LINK_STATUS, &status); + if (!status) { + glGetProgramInfoLog(shader->program, sizeof msg, NULL, msg); + weston_log("link info: %s\n", msg); + return -1; + } + + shader->proj_uniform = glGetUniformLocation(shader->program, "proj"); + shader->tex_uniforms[0] = glGetUniformLocation(shader->program, "tex"); + shader->tex_uniforms[1] = glGetUniformLocation(shader->program, "tex1"); + shader->tex_uniforms[2] = glGetUniformLocation(shader->program, "tex2"); + shader->alpha_uniform = glGetUniformLocation(shader->program, "alpha"); + shader->color_uniform = glGetUniformLocation(shader->program, "color"); + + return 0; +} + +static void +shader_release(struct gl_shader *shader) +{ + glDeleteShader(shader->vertex_shader); + glDeleteShader(shader->fragment_shader); + glDeleteProgram(shader->program); + + shader->vertex_shader = 0; + shader->fragment_shader = 0; + shader->program = 0; +} + +static void +log_extensions(const char *name, const char *extensions) +{ + const char *p, *end; + int l; + int len; + + l = weston_log("%s:", name); + p = extensions; + while (*p) { + end = strchrnul(p, ' '); + len = end - p; + if (l + len > 78) + l = weston_log_continue("\n" STAMP_SPACE "%.*s", + len, p); + else + l += weston_log_continue(" %.*s", len, p); + for (p = end; isspace(*p); p++) + ; + } + weston_log_continue("\n"); +} + +static void +log_egl_gl_info(EGLDisplay egldpy) +{ + const char *str; + + str = eglQueryString(egldpy, EGL_VERSION); + weston_log("EGL version: %s\n", str ? str : "(null)"); + + str = eglQueryString(egldpy, EGL_VENDOR); + weston_log("EGL vendor: %s\n", str ? str : "(null)"); + + str = eglQueryString(egldpy, EGL_CLIENT_APIS); + weston_log("EGL client APIs: %s\n", str ? str : "(null)"); + + str = eglQueryString(egldpy, EGL_EXTENSIONS); + log_extensions("EGL extensions", str ? str : "(null)"); + + str = (char *)glGetString(GL_VERSION); + weston_log("GL version: %s\n", str ? str : "(null)"); + + str = (char *)glGetString(GL_SHADING_LANGUAGE_VERSION); + weston_log("GLSL version: %s\n", str ? str : "(null)"); + + str = (char *)glGetString(GL_VENDOR); + weston_log("GL vendor: %s\n", str ? str : "(null)"); + + str = (char *)glGetString(GL_RENDERER); + weston_log("GL renderer: %s\n", str ? str : "(null)"); + + str = (char *)glGetString(GL_EXTENSIONS); + log_extensions("GL extensions", str ? str : "(null)"); +} + +static void +log_egl_config_info(EGLDisplay egldpy, EGLConfig eglconfig) +{ + EGLint r, g, b, a; + + weston_log("Chosen EGL config details:\n"); + + weston_log_continue(STAMP_SPACE "RGBA bits"); + if (eglGetConfigAttrib(egldpy, eglconfig, EGL_RED_SIZE, &r) && + eglGetConfigAttrib(egldpy, eglconfig, EGL_GREEN_SIZE, &g) && + eglGetConfigAttrib(egldpy, eglconfig, EGL_BLUE_SIZE, &b) && + eglGetConfigAttrib(egldpy, eglconfig, EGL_ALPHA_SIZE, &a)) + weston_log_continue(": %d %d %d %d\n", r, g, b, a); + else + weston_log_continue(" unknown\n"); + + weston_log_continue(STAMP_SPACE "swap interval range"); + if (eglGetConfigAttrib(egldpy, eglconfig, EGL_MIN_SWAP_INTERVAL, &a) && + eglGetConfigAttrib(egldpy, eglconfig, EGL_MAX_SWAP_INTERVAL, &b)) + weston_log_continue(": %d - %d\n", a, b); + else + weston_log_continue(" unknown\n"); +} + +static void +output_apply_border(struct weston_output *output, struct gl_renderer *gr) +{ + output->border.top = gr->border.top; + output->border.bottom = gr->border.bottom; + output->border.left = gr->border.left; + output->border.right = gr->border.right; +} + +WL_EXPORT void +gl_renderer_set_border(struct weston_compositor *ec, int32_t width, int32_t height, void *data, + int32_t *edges) +{ + struct gl_renderer *gr = get_renderer(ec); + struct weston_output *output; + + gr->border.left = edges[0]; + gr->border.right = edges[1]; + gr->border.top = edges[2]; + gr->border.bottom = edges[3]; + + gr->border.width = width; + gr->border.height = height; + + glGenTextures(1, &gr->border.texture); + glBindTexture(GL_TEXTURE_2D, gr->border.texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_EXT, + width, + height, + 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, + data); + + wl_list_for_each(output, &ec->output_list, link) + output_apply_border(output, gr); +} + +static int +gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface); + +WL_EXPORT int +gl_renderer_output_create(struct weston_output *output, + EGLNativeWindowType window) +{ + struct weston_compositor *ec = output->compositor; + struct gl_renderer *gr = get_renderer(ec); + struct gl_output_state *go = calloc(1, sizeof *go); + int i; + + if (!go) + return -1; + + go->egl_surface = + eglCreateWindowSurface(gr->egl_display, + gr->egl_config, + window, NULL); + + if (go->egl_surface == EGL_NO_SURFACE) { + weston_log("failed to create egl surface\n"); + free(go); + return -1; + } + + if (gr->egl_context == NULL) + if (gl_renderer_setup(ec, go->egl_surface) < 0) { + free(go); + return -1; + } + + for (i = 0; i < BUFFER_DAMAGE_COUNT; i++) + pixman_region32_init(&go->buffer_damage[i]); + + output->renderer_state = go; + + output_apply_border(output, gr); + + return 0; +} + +WL_EXPORT void +gl_renderer_output_destroy(struct weston_output *output) +{ + struct gl_renderer *gr = get_renderer(output->compositor); + struct gl_output_state *go = get_output_state(output); + int i; + + for (i = 0; i < 2; i++) + pixman_region32_fini(&go->buffer_damage[i]); + + eglDestroySurface(gr->egl_display, go->egl_surface); + + free(go); +} + +WL_EXPORT EGLSurface +gl_renderer_output_surface(struct weston_output *output) +{ + return get_output_state(output)->egl_surface; +} + +static void +gl_renderer_destroy(struct weston_compositor *ec) +{ + struct gl_renderer *gr = get_renderer(ec); + + if (gr->has_bind_display) + gr->unbind_display(gr->egl_display, ec->wl_display); + + /* Work around crash in egl_dri2.c's dri2_make_current() - when does this apply? */ + eglMakeCurrent(gr->egl_display, + EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + + eglTerminate(gr->egl_display); + eglReleaseThread(); + + wl_array_release(&gr->vertices); + wl_array_release(&gr->indices); + wl_array_release(&gr->vtxcnt); + + free(gr); +} + +static int +egl_choose_config(struct gl_renderer *gr, const EGLint *attribs, + const EGLint *visual_id) +{ + EGLint count = 0; + EGLint matched = 0; + EGLConfig *configs; + int i; + + if (!eglGetConfigs(gr->egl_display, NULL, 0, &count) || count < 1) + return -1; + + configs = calloc(count, sizeof *configs); + if (!configs) + return -1; + + if (!eglChooseConfig(gr->egl_display, attribs, configs, + count, &matched)) + goto out; + + for (i = 0; i < matched; ++i) { + EGLint id; + + if (visual_id) { + if (!eglGetConfigAttrib(gr->egl_display, + configs[i], EGL_NATIVE_VISUAL_ID, + &id)) + continue; + + if (id != 0 && id != *visual_id) + continue; + } + + gr->egl_config = configs[i]; + + free(configs); + return 0; + } + +out: + free(configs); + return -1; +} + +WL_EXPORT const EGLint gl_renderer_opaque_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 0, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE +}; + +WL_EXPORT const EGLint gl_renderer_alpha_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE +}; + +WL_EXPORT int +gl_renderer_create(struct weston_compositor *ec, EGLNativeDisplayType display, + const EGLint *attribs, const EGLint *visual_id) +{ + struct gl_renderer *gr; + EGLint major, minor; + + gr = calloc(1, sizeof *gr); + + if (gr == NULL) + return -1; + + gr->base.read_pixels = gl_renderer_read_pixels; + gr->base.repaint_output = gl_renderer_repaint_output; + gr->base.flush_damage = gl_renderer_flush_damage; + gr->base.attach = gl_renderer_attach; + gr->base.create_surface = gl_renderer_create_surface; + gr->base.surface_set_color = gl_renderer_surface_set_color; + gr->base.destroy_surface = gl_renderer_destroy_surface; + gr->base.destroy = gl_renderer_destroy; + + gr->egl_display = eglGetDisplay(display); + if (gr->egl_display == EGL_NO_DISPLAY) { + weston_log("failed to create display\n"); + goto err_egl; + } + + if (!eglInitialize(gr->egl_display, &major, &minor)) { + weston_log("failed to initialize display\n"); + goto err_egl; + } + + if (egl_choose_config(gr, attribs, visual_id) < 0) { + weston_log("failed to choose EGL config\n"); + goto err_egl; + } + + ec->renderer = &gr->base; + ec->capabilities |= WESTON_CAP_ROTATION_ANY; + ec->capabilities |= WESTON_CAP_CAPTURE_YFLIP; + + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_RGB565); + + return 0; + +err_egl: + gl_renderer_print_egl_error_state(); + free(gr); + return -1; +} + +WL_EXPORT EGLDisplay +gl_renderer_display(struct weston_compositor *ec) +{ + return get_renderer(ec)->egl_display; +} + +static int +compile_shaders(struct weston_compositor *ec) +{ + struct gl_renderer *gr = get_renderer(ec); + + gr->texture_shader_rgba.vertex_source = vertex_shader; + gr->texture_shader_rgba.fragment_source = texture_fragment_shader_rgba; + + gr->texture_shader_rgbx.vertex_source = vertex_shader; + gr->texture_shader_rgbx.fragment_source = texture_fragment_shader_rgbx; + + gr->texture_shader_egl_external.vertex_source = vertex_shader; + gr->texture_shader_egl_external.fragment_source = + texture_fragment_shader_egl_external; + + gr->texture_shader_y_uv.vertex_source = vertex_shader; + gr->texture_shader_y_uv.fragment_source = texture_fragment_shader_y_uv; + + gr->texture_shader_y_u_v.vertex_source = vertex_shader; + gr->texture_shader_y_u_v.fragment_source = + texture_fragment_shader_y_u_v; + + gr->texture_shader_y_u_v.vertex_source = vertex_shader; + gr->texture_shader_y_xuxv.fragment_source = + texture_fragment_shader_y_xuxv; + + gr->solid_shader.vertex_source = vertex_shader; + gr->solid_shader.fragment_source = solid_fragment_shader; + + return 0; +} + +static void +fragment_debug_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + struct weston_compositor *ec = data; + struct gl_renderer *gr = get_renderer(ec); + struct weston_output *output; + + gr->fragment_shader_debug ^= 1; + + shader_release(&gr->texture_shader_rgba); + shader_release(&gr->texture_shader_rgbx); + shader_release(&gr->texture_shader_egl_external); + shader_release(&gr->texture_shader_y_uv); + shader_release(&gr->texture_shader_y_u_v); + shader_release(&gr->texture_shader_y_xuxv); + shader_release(&gr->solid_shader); + + /* Force use_shader() to call glUseProgram(), since we need to use + * the recompiled version of the shader. */ + gr->current_shader = NULL; + + wl_list_for_each(output, &ec->output_list, link) + weston_output_damage(output); +} + +static void +fan_debug_repaint_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + struct weston_compositor *compositor = data; + struct gl_renderer *gr = get_renderer(compositor); + + gr->fan_debug = !gr->fan_debug; + weston_compositor_damage_all(compositor); +} + +static int +gl_renderer_setup(struct weston_compositor *ec, EGLSurface egl_surface) +{ + struct gl_renderer *gr = get_renderer(ec); + const char *extensions; + EGLBoolean ret; + + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + weston_log("failed to bind EGL_OPENGL_ES_API\n"); + gl_renderer_print_egl_error_state(); + return -1; + } + + log_egl_config_info(gr->egl_display, gr->egl_config); + + gr->egl_context = eglCreateContext(gr->egl_display, gr->egl_config, + EGL_NO_CONTEXT, context_attribs); + if (gr->egl_context == NULL) { + weston_log("failed to create context\n"); + gl_renderer_print_egl_error_state(); + return -1; + } + + ret = eglMakeCurrent(gr->egl_display, egl_surface, + egl_surface, gr->egl_context); + if (ret == EGL_FALSE) { + weston_log("Failed to make EGL context current.\n"); + gl_renderer_print_egl_error_state(); + return -1; + } + + log_egl_gl_info(gr->egl_display); + + gr->image_target_texture_2d = + (void *) eglGetProcAddress("glEGLImageTargetTexture2DOES"); + gr->create_image = (void *) eglGetProcAddress("eglCreateImageKHR"); + gr->destroy_image = (void *) eglGetProcAddress("eglDestroyImageKHR"); + gr->bind_display = + (void *) eglGetProcAddress("eglBindWaylandDisplayWL"); + gr->unbind_display = + (void *) eglGetProcAddress("eglUnbindWaylandDisplayWL"); + gr->query_buffer = + (void *) eglGetProcAddress("eglQueryWaylandBufferWL"); + + extensions = (const char *) glGetString(GL_EXTENSIONS); + if (!extensions) { + weston_log("Retrieving GL extension string failed.\n"); + return -1; + } + + if (!strstr(extensions, "GL_EXT_texture_format_BGRA8888")) { + weston_log("GL_EXT_texture_format_BGRA8888 not available\n"); + return -1; + } + + if (strstr(extensions, "GL_EXT_read_format_bgra")) + ec->read_format = PIXMAN_a8r8g8b8; + else + ec->read_format = PIXMAN_a8b8g8r8; + +#ifdef GL_EXT_unpack_subimage + if (strstr(extensions, "GL_EXT_unpack_subimage")) + gr->has_unpack_subimage = 1; +#endif + + if (strstr(extensions, "GL_OES_EGL_image_external")) + gr->has_egl_image_external = 1; + + extensions = + (const char *) eglQueryString(gr->egl_display, EGL_EXTENSIONS); + if (!extensions) { + weston_log("Retrieving EGL extension string failed.\n"); + return -1; + } + + if (strstr(extensions, "EGL_WL_bind_wayland_display")) + gr->has_bind_display = 1; + if (gr->has_bind_display) { + ret = gr->bind_display(gr->egl_display, ec->wl_display); + if (!ret) + gr->has_bind_display = 0; + } + + if (strstr(extensions, "EGL_EXT_buffer_age")) + gr->has_egl_buffer_age = 1; + else + weston_log("warning: EGL_EXT_buffer_age not supported. " + "Performance could be affected.\n"); + + glActiveTexture(GL_TEXTURE0); + + if (compile_shaders(ec)) + return -1; + + weston_compositor_add_debug_binding(ec, KEY_S, + fragment_debug_binding, ec); + weston_compositor_add_debug_binding(ec, KEY_F, + fan_debug_repaint_binding, ec); + + weston_log("GL ES 2 renderer features:\n"); + weston_log_continue(STAMP_SPACE "read-back format: %s\n", + ec->read_format == PIXMAN_a8r8g8b8 ? "BGRA" : "RGBA"); + weston_log_continue(STAMP_SPACE "wl_shm sub-image to texture: %s\n", + gr->has_unpack_subimage ? "yes" : "no"); + weston_log_continue(STAMP_SPACE "EGL Wayland extension: %s\n", + gr->has_bind_display ? "yes" : "no"); + + + return 0; +} diff --git a/src/gl-renderer.h b/src/gl-renderer.h new file mode 100644 index 00000000..d16ade29 --- /dev/null +++ b/src/gl-renderer.h @@ -0,0 +1,106 @@ +/* + * Copyright © 2012 John Kåre Alsaker + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include "compositor.h" + +#ifdef ENABLE_EGL + +#include + +extern const EGLint gl_renderer_opaque_attribs[]; +extern const EGLint gl_renderer_alpha_attribs[]; + +int +gl_renderer_create(struct weston_compositor *ec, EGLNativeDisplayType display, + const EGLint *attribs, const EGLint *visual_id); +EGLDisplay +gl_renderer_display(struct weston_compositor *ec); +int +gl_renderer_output_create(struct weston_output *output, + EGLNativeWindowType window); +void +gl_renderer_output_destroy(struct weston_output *output); +EGLSurface +gl_renderer_output_surface(struct weston_output *output); +void +gl_renderer_set_border(struct weston_compositor *ec, int32_t width, int32_t height, void *data, + int32_t *edges); + +void +gl_renderer_print_egl_error_state(void); +#else + +typedef int EGLint; +typedef void *EGLDisplay; +typedef void *EGLSurface; +typedef intptr_t EGLNativeDisplayType; +typedef intptr_t EGLNativeWindowType; +#define EGL_DEFAULT_DISPLAY NULL + +static const EGLint gl_renderer_opaque_attribs[]; +static const EGLint gl_renderer_alpha_attribs[]; + +inline static int +gl_renderer_create(struct weston_compositor *ec, EGLNativeDisplayType display, + const EGLint *attribs, const EGLint *visual_id) +{ + return -1; +} + +inline static EGLDisplay +gl_renderer_display(struct weston_compositor *ec) +{ + return 0; +} + +inline static int +gl_renderer_output_create(struct weston_output *output, + EGLNativeWindowType window) +{ + return -1; +} + +inline static void +gl_renderer_output_destroy(struct weston_output *output) +{ +} + +inline static EGLSurface +gl_renderer_output_surface(struct weston_output *output) +{ + return 0; +} + +inline static void +gl_renderer_set_border(struct weston_compositor *ec, int32_t width, int32_t height, void *data, + int32_t *edges) +{ +} + +inline static void +gl_renderer_print_egl_error_state(void) +{ +} + +#endif diff --git a/src/input.c b/src/input.c new file mode 100644 index 00000000..dd8e4973 --- /dev/null +++ b/src/input.c @@ -0,0 +1,1851 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "../shared/os-compatibility.h" +#include "compositor.h" + +static void +empty_region(pixman_region32_t *region) +{ + pixman_region32_fini(region); + pixman_region32_init(region); +} + +static void unbind_resource(struct wl_resource *resource) +{ + wl_list_remove(wl_resource_get_link(resource)); +} + +WL_EXPORT void +weston_seat_repick(struct weston_seat *seat) +{ + const struct weston_pointer *pointer = seat->pointer; + + if (pointer == NULL) + return; + + pointer->grab->interface->focus(seat->pointer->grab); +} + +static void +weston_compositor_idle_inhibit(struct weston_compositor *compositor) +{ + weston_compositor_wake(compositor); + compositor->idle_inhibit++; +} + +static void +weston_compositor_idle_release(struct weston_compositor *compositor) +{ + compositor->idle_inhibit--; + weston_compositor_wake(compositor); +} + +static void +move_resources(struct wl_list *destination, struct wl_list *source) +{ + wl_list_insert_list(destination, source); + wl_list_init(source); +} + +static void +move_resources_for_client(struct wl_list *destination, + struct wl_list *source, + struct wl_client *client) +{ + struct wl_resource *resource, *tmp; + wl_resource_for_each_safe(resource, tmp, source) { + if (wl_resource_get_client(resource) == client) { + wl_list_remove(wl_resource_get_link(resource)); + wl_list_insert(destination, + wl_resource_get_link(resource)); + } + } +} + +static void +default_grab_focus(struct weston_pointer_grab *grab) +{ + struct weston_pointer *pointer = grab->pointer; + struct weston_surface *surface; + wl_fixed_t sx, sy; + + if (pointer->button_count > 0) + return; + + surface = weston_compositor_pick_surface(pointer->seat->compositor, + pointer->x, pointer->y, + &sx, &sy); + + if (pointer->focus != surface) + weston_pointer_set_focus(pointer, surface, sx, sy); +} + +static void +default_grab_motion(struct weston_pointer_grab *grab, uint32_t time) +{ + struct weston_pointer *pointer = grab->pointer; + wl_fixed_t sx, sy; + struct wl_list *resource_list; + struct wl_resource *resource; + + resource_list = &pointer->focus_resource_list; + wl_resource_for_each(resource, resource_list) { + weston_surface_from_global_fixed(pointer->focus, + pointer->x, pointer->y, + &sx, &sy); + wl_pointer_send_motion(resource, time, sx, sy); + } +} + +static void +default_grab_button(struct weston_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state_w) +{ + struct weston_pointer *pointer = grab->pointer; + struct weston_compositor *compositor = pointer->seat->compositor; + struct weston_surface *surface; + struct wl_resource *resource; + uint32_t serial; + enum wl_pointer_button_state state = state_w; + struct wl_display *display = compositor->wl_display; + wl_fixed_t sx, sy; + struct wl_list *resource_list; + + resource_list = &pointer->focus_resource_list; + if (!wl_list_empty(resource_list)) { + serial = wl_display_next_serial(display); + wl_resource_for_each(resource, resource_list) + wl_pointer_send_button(resource, + serial, + time, + button, + state_w); + } + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + surface = weston_compositor_pick_surface(compositor, + pointer->x, + pointer->y, + &sx, &sy); + + weston_pointer_set_focus(pointer, surface, sx, sy); + } +} + +static void +default_grab_pointer_cancel(struct weston_pointer_grab *grab) +{ +} + +static const struct weston_pointer_grab_interface + default_pointer_grab_interface = { + default_grab_focus, + default_grab_motion, + default_grab_button, + default_grab_pointer_cancel, +}; + +static void +default_grab_touch_down(struct weston_touch_grab *grab, uint32_t time, + int touch_id, wl_fixed_t sx, wl_fixed_t sy) +{ + struct weston_touch *touch = grab->touch; + struct wl_display *display = touch->seat->compositor->wl_display; + uint32_t serial; + struct wl_resource *resource; + struct wl_list *resource_list; + + resource_list = &touch->focus_resource_list; + + if (!wl_list_empty(resource_list) && touch->focus) { + serial = wl_display_next_serial(display); + wl_resource_for_each(resource, resource_list) + wl_touch_send_down(resource, serial, time, + touch->focus->resource, + touch_id, sx, sy); + } +} + +static void +default_grab_touch_up(struct weston_touch_grab *grab, + uint32_t time, int touch_id) +{ + struct weston_touch *touch = grab->touch; + struct wl_display *display = touch->seat->compositor->wl_display; + uint32_t serial; + struct wl_resource *resource; + struct wl_list *resource_list; + + resource_list = &touch->focus_resource_list; + + if (!wl_list_empty(resource_list)) { + serial = wl_display_next_serial(display); + wl_resource_for_each(resource, resource_list) + wl_touch_send_up(resource, serial, time, touch_id); + } +} + +static void +default_grab_touch_motion(struct weston_touch_grab *grab, uint32_t time, + int touch_id, wl_fixed_t sx, wl_fixed_t sy) +{ + struct weston_touch *touch = grab->touch; + struct wl_resource *resource; + struct wl_list *resource_list; + + resource_list = &touch->focus_resource_list; + + wl_resource_for_each(resource, resource_list) { + wl_touch_send_motion(resource, time, + touch_id, sx, sy); + } +} + +static void +default_grab_touch_cancel(struct weston_touch_grab *grab) +{ +} + +static const struct weston_touch_grab_interface default_touch_grab_interface = { + default_grab_touch_down, + default_grab_touch_up, + default_grab_touch_motion, + default_grab_touch_cancel, +}; + +static void +default_grab_key(struct weston_keyboard_grab *grab, + uint32_t time, uint32_t key, uint32_t state) +{ + struct weston_keyboard *keyboard = grab->keyboard; + struct wl_resource *resource; + struct wl_display *display = keyboard->seat->compositor->wl_display; + uint32_t serial; + struct wl_list *resource_list; + + resource_list = &keyboard->focus_resource_list; + if (!wl_list_empty(resource_list)) { + serial = wl_display_next_serial(display); + wl_resource_for_each(resource, resource_list) + wl_keyboard_send_key(resource, + serial, + time, + key, + state); + } +} + +static void +send_modifiers_to_resource(struct weston_keyboard *keyboard, + struct wl_resource *resource, + uint32_t serial) +{ + wl_keyboard_send_modifiers(resource, + serial, + keyboard->modifiers.mods_depressed, + keyboard->modifiers.mods_latched, + keyboard->modifiers.mods_locked, + keyboard->modifiers.group); +} + +static void +send_modifiers_to_client_in_list(struct wl_client *client, + struct wl_list *list, + uint32_t serial, + struct weston_keyboard *keyboard) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, list) { + if (wl_resource_get_client(resource) == client) + send_modifiers_to_resource(keyboard, + resource, + serial); + } +} + +static struct wl_resource * +find_resource_for_surface(struct wl_list *list, struct weston_surface *surface) +{ + if (!surface) + return NULL; + + if (!surface->resource) + return NULL; + + return wl_resource_find_for_client(list, wl_resource_get_client(surface->resource)); +} + +static void +default_grab_modifiers(struct weston_keyboard_grab *grab, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + struct weston_keyboard *keyboard = grab->keyboard; + struct weston_pointer *pointer = grab->keyboard->seat->pointer; + struct wl_resource *resource; + struct wl_list *resource_list; + + resource_list = &keyboard->focus_resource_list; + + wl_resource_for_each(resource, resource_list) { + wl_keyboard_send_modifiers(resource, serial, mods_depressed, + mods_latched, mods_locked, group); + } + if (pointer && pointer->focus && pointer->focus != keyboard->focus) { + struct wl_client *pointer_client = + wl_resource_get_client(pointer->focus->resource); + send_modifiers_to_client_in_list(pointer_client, + &keyboard->resource_list, + serial, + keyboard); + } +} + +static void +default_grab_keyboard_cancel(struct weston_keyboard_grab *grab) +{ +} + +static const struct weston_keyboard_grab_interface + default_keyboard_grab_interface = { + default_grab_key, + default_grab_modifiers, + default_grab_keyboard_cancel, +}; + +static void +pointer_unmap_sprite(struct weston_pointer *pointer) +{ + if (weston_surface_is_mapped(pointer->sprite)) + weston_surface_unmap(pointer->sprite); + + wl_list_remove(&pointer->sprite_destroy_listener.link); + pointer->sprite->configure = NULL; + pointer->sprite->configure_private = NULL; + pointer->sprite = NULL; +} + +static void +pointer_handle_sprite_destroy(struct wl_listener *listener, void *data) +{ + struct weston_pointer *pointer = + container_of(listener, struct weston_pointer, + sprite_destroy_listener); + + pointer->sprite = NULL; +} + +WL_EXPORT struct weston_pointer * +weston_pointer_create(void) +{ + struct weston_pointer *pointer; + + pointer = zalloc(sizeof *pointer); + if (pointer == NULL) + return NULL; + + wl_list_init(&pointer->resource_list); + wl_list_init(&pointer->focus_resource_list); + pointer->default_grab.interface = &default_pointer_grab_interface; + pointer->default_grab.pointer = pointer; + pointer->grab = &pointer->default_grab; + wl_signal_init(&pointer->focus_signal); + + pointer->sprite_destroy_listener.notify = pointer_handle_sprite_destroy; + + /* FIXME: Pick better co-ords. */ + pointer->x = wl_fixed_from_int(100); + pointer->y = wl_fixed_from_int(100); + + return pointer; +} + +WL_EXPORT void +weston_pointer_destroy(struct weston_pointer *pointer) +{ + if (pointer->sprite) + pointer_unmap_sprite(pointer); + + /* XXX: What about pointer->resource_list? */ + + free(pointer); +} + +WL_EXPORT struct weston_keyboard * +weston_keyboard_create(void) +{ + struct weston_keyboard *keyboard; + + keyboard = zalloc(sizeof *keyboard); + if (keyboard == NULL) + return NULL; + + wl_list_init(&keyboard->resource_list); + wl_list_init(&keyboard->focus_resource_list); + wl_array_init(&keyboard->keys); + keyboard->default_grab.interface = &default_keyboard_grab_interface; + keyboard->default_grab.keyboard = keyboard; + keyboard->grab = &keyboard->default_grab; + wl_signal_init(&keyboard->focus_signal); + + return keyboard; +} + +WL_EXPORT void +weston_keyboard_destroy(struct weston_keyboard *keyboard) +{ + /* XXX: What about keyboard->resource_list? */ + + wl_array_release(&keyboard->keys); + free(keyboard); +} + +WL_EXPORT struct weston_touch * +weston_touch_create(void) +{ + struct weston_touch *touch; + + touch = zalloc(sizeof *touch); + if (touch == NULL) + return NULL; + + wl_list_init(&touch->resource_list); + wl_list_init(&touch->focus_resource_list); + touch->default_grab.interface = &default_touch_grab_interface; + touch->default_grab.touch = touch; + touch->grab = &touch->default_grab; + wl_signal_init(&touch->focus_signal); + + return touch; +} + +WL_EXPORT void +weston_touch_destroy(struct weston_touch *touch) +{ + /* XXX: What about touch->resource_list? */ + + free(touch); +} + +static void +seat_send_updated_caps(struct weston_seat *seat) +{ + enum wl_seat_capability caps = 0; + struct wl_resource *resource; + + if (seat->pointer_device_count > 0) + caps |= WL_SEAT_CAPABILITY_POINTER; + if (seat->keyboard_device_count > 0) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + if (seat->touch_device_count > 0) + caps |= WL_SEAT_CAPABILITY_TOUCH; + + wl_resource_for_each(resource, &seat->base_resource_list) { + wl_seat_send_capabilities(resource, caps); + } +} + +WL_EXPORT void +weston_pointer_set_focus(struct weston_pointer *pointer, + struct weston_surface *surface, + wl_fixed_t sx, wl_fixed_t sy) +{ + struct weston_keyboard *kbd = pointer->seat->keyboard; + struct wl_resource *resource; + struct wl_display *display = pointer->seat->compositor->wl_display; + uint32_t serial; + struct wl_list *focus_resource_list; + + focus_resource_list = &pointer->focus_resource_list; + + if (!wl_list_empty(focus_resource_list) && pointer->focus != surface) { + serial = wl_display_next_serial(display); + wl_resource_for_each(resource, focus_resource_list) { + wl_pointer_send_leave(resource, serial, + pointer->focus->resource); + } + + move_resources(&pointer->resource_list, focus_resource_list); + } + + if (find_resource_for_surface(&pointer->resource_list, surface) && + pointer->focus != surface) { + struct wl_client *surface_client = + wl_resource_get_client(surface->resource); + + serial = wl_display_next_serial(display); + + if (kbd && kbd->focus != pointer->focus) + send_modifiers_to_client_in_list(surface_client, + &kbd->resource_list, + serial, + kbd); + + move_resources_for_client(focus_resource_list, + &pointer->resource_list, + surface_client); + + wl_resource_for_each(resource, focus_resource_list) { + wl_pointer_send_enter(resource, + serial, + surface->resource, + sx, sy); + } + + pointer->focus_serial = serial; + } + + pointer->focus = surface; + wl_signal_emit(&pointer->focus_signal, pointer); +} + +static void +send_enter_to_resource_list(struct wl_list *list, + struct weston_keyboard *keyboard, + struct weston_surface *surface, + uint32_t serial) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, list) { + send_modifiers_to_resource(keyboard, resource, serial); + wl_keyboard_send_enter(resource, serial, + surface->resource, + &keyboard->keys); + } +} + +WL_EXPORT void +weston_keyboard_set_focus(struct weston_keyboard *keyboard, + struct weston_surface *surface) +{ + struct wl_resource *resource; + struct wl_display *display = keyboard->seat->compositor->wl_display; + uint32_t serial; + struct wl_list *focus_resource_list; + + focus_resource_list = &keyboard->focus_resource_list; + + if (!wl_list_empty(focus_resource_list) && keyboard->focus != surface) { + serial = wl_display_next_serial(display); + wl_resource_for_each(resource, focus_resource_list) { + wl_keyboard_send_leave(resource, serial, + keyboard->focus->resource); + } + move_resources(&keyboard->resource_list, focus_resource_list); + } + + if (find_resource_for_surface(&keyboard->resource_list, surface) && + keyboard->focus != surface) { + struct wl_client *surface_client = + wl_resource_get_client(surface->resource); + + serial = wl_display_next_serial(display); + + move_resources_for_client(focus_resource_list, + &keyboard->resource_list, + surface_client); + send_enter_to_resource_list(focus_resource_list, + keyboard, + surface, + serial); + keyboard->focus_serial = serial; + } + + keyboard->focus = surface; + wl_signal_emit(&keyboard->focus_signal, keyboard); +} + +WL_EXPORT void +weston_keyboard_start_grab(struct weston_keyboard *keyboard, + struct weston_keyboard_grab *grab) +{ + keyboard->grab = grab; + grab->keyboard = keyboard; + + /* XXX focus? */ +} + +WL_EXPORT void +weston_keyboard_end_grab(struct weston_keyboard *keyboard) +{ + keyboard->grab = &keyboard->default_grab; +} + +static void +weston_keyboard_cancel_grab(struct weston_keyboard *keyboard) +{ + keyboard->grab->interface->cancel(keyboard->grab); +} + +WL_EXPORT void +weston_pointer_start_grab(struct weston_pointer *pointer, + struct weston_pointer_grab *grab) +{ + pointer->grab = grab; + grab->pointer = pointer; + pointer->grab->interface->focus(pointer->grab); +} + +WL_EXPORT void +weston_pointer_end_grab(struct weston_pointer *pointer) +{ + pointer->grab = &pointer->default_grab; + pointer->grab->interface->focus(pointer->grab); +} + +static void +weston_pointer_cancel_grab(struct weston_pointer *pointer) +{ + pointer->grab->interface->cancel(pointer->grab); +} + +WL_EXPORT void +weston_touch_start_grab(struct weston_touch *touch, struct weston_touch_grab *grab) +{ + touch->grab = grab; + grab->touch = touch; +} + +WL_EXPORT void +weston_touch_end_grab(struct weston_touch *touch) +{ + touch->grab = &touch->default_grab; +} + +static void +weston_touch_cancel_grab(struct weston_touch *touch) +{ + touch->grab->interface->cancel(touch->grab); +} + +WL_EXPORT void +weston_pointer_clamp(struct weston_pointer *pointer, wl_fixed_t *fx, wl_fixed_t *fy) +{ + struct weston_compositor *ec = pointer->seat->compositor; + struct weston_output *output, *prev = NULL; + int x, y, old_x, old_y, valid = 0; + + x = wl_fixed_to_int(*fx); + y = wl_fixed_to_int(*fy); + old_x = wl_fixed_to_int(pointer->x); + old_y = wl_fixed_to_int(pointer->y); + + wl_list_for_each(output, &ec->output_list, link) { + if (pointer->seat->output && pointer->seat->output != output) + continue; + if (pixman_region32_contains_point(&output->region, + x, y, NULL)) + valid = 1; + if (pixman_region32_contains_point(&output->region, + old_x, old_y, NULL)) + prev = output; + } + + if (!prev) + prev = pointer->seat->output; + + if (prev && !valid) { + if (x < prev->x) + *fx = wl_fixed_from_int(prev->x); + else if (x >= prev->x + prev->width) + *fx = wl_fixed_from_int(prev->x + + prev->width - 1); + if (y < prev->y) + *fy = wl_fixed_from_int(prev->y); + else if (y >= prev->y + prev->height) + *fy = wl_fixed_from_int(prev->y + + prev->height - 1); + } +} + +/* Takes absolute values */ +static void +move_pointer(struct weston_seat *seat, wl_fixed_t x, wl_fixed_t y) +{ + struct weston_compositor *ec = seat->compositor; + struct weston_pointer *pointer = seat->pointer; + struct weston_output *output; + int32_t ix, iy; + + weston_pointer_clamp (pointer, &x, &y); + + pointer->x = x; + pointer->y = y; + + ix = wl_fixed_to_int(x); + iy = wl_fixed_to_int(y); + + wl_list_for_each(output, &ec->output_list, link) + if (output->zoom.active && + pixman_region32_contains_point(&output->region, + ix, iy, NULL)) + weston_output_update_zoom(output, ZOOM_FOCUS_POINTER); + + if (pointer->sprite) { + weston_surface_set_position(pointer->sprite, + ix - pointer->hotspot_x, + iy - pointer->hotspot_y); + weston_surface_schedule_repaint(pointer->sprite); + } +} + +WL_EXPORT void +notify_motion(struct weston_seat *seat, + uint32_t time, wl_fixed_t dx, wl_fixed_t dy) +{ + struct weston_compositor *ec = seat->compositor; + struct weston_pointer *pointer = seat->pointer; + + weston_compositor_wake(ec); + + move_pointer(seat, pointer->x + dx, pointer->y + dy); + + pointer->grab->interface->focus(pointer->grab); + pointer->grab->interface->motion(pointer->grab, time); +} + +WL_EXPORT void +notify_motion_absolute(struct weston_seat *seat, + uint32_t time, wl_fixed_t x, wl_fixed_t y) +{ + struct weston_compositor *ec = seat->compositor; + struct weston_pointer *pointer = seat->pointer; + + weston_compositor_wake(ec); + + move_pointer(seat, x, y); + + pointer->grab->interface->focus(pointer->grab); + pointer->grab->interface->motion(pointer->grab, time); +} + +WL_EXPORT void +weston_surface_activate(struct weston_surface *surface, + struct weston_seat *seat) +{ + struct weston_compositor *compositor = seat->compositor; + + if (seat->keyboard) { + weston_keyboard_set_focus(seat->keyboard, surface); + wl_data_device_set_keyboard_focus(seat); + } + + wl_signal_emit(&compositor->activate_signal, surface); +} + +WL_EXPORT void +notify_button(struct weston_seat *seat, uint32_t time, int32_t button, + enum wl_pointer_button_state state) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = seat->pointer; + struct weston_surface *focus = + (struct weston_surface *) pointer->focus; + uint32_t serial = wl_display_next_serial(compositor->wl_display); + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (compositor->ping_handler && focus) + compositor->ping_handler(focus, serial); + weston_compositor_idle_inhibit(compositor); + if (pointer->button_count == 0) { + pointer->grab_button = button; + pointer->grab_time = time; + pointer->grab_x = pointer->x; + pointer->grab_y = pointer->y; + } + pointer->button_count++; + } else { + weston_compositor_idle_release(compositor); + pointer->button_count--; + } + + weston_compositor_run_button_binding(compositor, seat, time, button, + state); + + pointer->grab->interface->button(pointer->grab, time, button, state); + + if (pointer->button_count == 1) + pointer->grab_serial = + wl_display_get_serial(compositor->wl_display); +} + +WL_EXPORT void +notify_axis(struct weston_seat *seat, uint32_t time, uint32_t axis, + wl_fixed_t value) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_pointer *pointer = seat->pointer; + struct weston_surface *focus = + (struct weston_surface *) pointer->focus; + uint32_t serial = wl_display_next_serial(compositor->wl_display); + struct wl_resource *resource; + struct wl_list *resource_list; + + if (compositor->ping_handler && focus) + compositor->ping_handler(focus, serial); + + weston_compositor_wake(compositor); + + if (!value) + return; + + if (weston_compositor_run_axis_binding(compositor, seat, + time, axis, value)) + return; + + resource_list = &pointer->focus_resource_list; + wl_resource_for_each(resource, resource_list) + wl_pointer_send_axis(resource, time, axis, + value); +} + +#ifdef ENABLE_XKBCOMMON +WL_EXPORT void +notify_modifiers(struct weston_seat *seat, uint32_t serial) +{ + struct weston_keyboard *keyboard = seat->keyboard; + struct weston_keyboard_grab *grab = keyboard->grab; + uint32_t mods_depressed, mods_latched, mods_locked, group; + uint32_t mods_lookup; + enum weston_led leds = 0; + int changed = 0; + + /* Serialize and update our internal state, checking to see if it's + * different to the previous state. */ + mods_depressed = xkb_state_serialize_mods(seat->xkb_state.state, + XKB_STATE_DEPRESSED); + mods_latched = xkb_state_serialize_mods(seat->xkb_state.state, + XKB_STATE_LATCHED); + mods_locked = xkb_state_serialize_mods(seat->xkb_state.state, + XKB_STATE_LOCKED); + group = xkb_state_serialize_group(seat->xkb_state.state, + XKB_STATE_EFFECTIVE); + + if (mods_depressed != seat->keyboard->modifiers.mods_depressed || + mods_latched != seat->keyboard->modifiers.mods_latched || + mods_locked != seat->keyboard->modifiers.mods_locked || + group != seat->keyboard->modifiers.group) + changed = 1; + + seat->keyboard->modifiers.mods_depressed = mods_depressed; + seat->keyboard->modifiers.mods_latched = mods_latched; + seat->keyboard->modifiers.mods_locked = mods_locked; + seat->keyboard->modifiers.group = group; + + /* And update the modifier_state for bindings. */ + mods_lookup = mods_depressed | mods_latched; + seat->modifier_state = 0; + if (mods_lookup & (1 << seat->xkb_info->ctrl_mod)) + seat->modifier_state |= MODIFIER_CTRL; + if (mods_lookup & (1 << seat->xkb_info->alt_mod)) + seat->modifier_state |= MODIFIER_ALT; + if (mods_lookup & (1 << seat->xkb_info->super_mod)) + seat->modifier_state |= MODIFIER_SUPER; + if (mods_lookup & (1 << seat->xkb_info->shift_mod)) + seat->modifier_state |= MODIFIER_SHIFT; + + /* Finally, notify the compositor that LEDs have changed. */ + if (xkb_state_led_index_is_active(seat->xkb_state.state, + seat->xkb_info->num_led)) + leds |= LED_NUM_LOCK; + if (xkb_state_led_index_is_active(seat->xkb_state.state, + seat->xkb_info->caps_led)) + leds |= LED_CAPS_LOCK; + if (xkb_state_led_index_is_active(seat->xkb_state.state, + seat->xkb_info->scroll_led)) + leds |= LED_SCROLL_LOCK; + if (leds != seat->xkb_state.leds && seat->led_update) + seat->led_update(seat, leds); + seat->xkb_state.leds = leds; + + if (changed) { + grab->interface->modifiers(grab, + serial, + keyboard->modifiers.mods_depressed, + keyboard->modifiers.mods_latched, + keyboard->modifiers.mods_locked, + keyboard->modifiers.group); + } +} + +static void +update_modifier_state(struct weston_seat *seat, uint32_t serial, uint32_t key, + enum wl_keyboard_key_state state) +{ + enum xkb_key_direction direction; + + /* Keyboard modifiers don't exist in raw keyboard mode */ + if (!seat->compositor->use_xkbcommon) + return; + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + direction = XKB_KEY_DOWN; + else + direction = XKB_KEY_UP; + + /* Offset the keycode by 8, as the evdev XKB rules reflect X's + * broken keycode system, which starts at 8. */ + xkb_state_update_key(seat->xkb_state.state, key + 8, direction); + + notify_modifiers(seat, serial); +} +#else +WL_EXPORT void +notify_modifiers(struct weston_seat *seat, uint32_t serial) +{ +} + +static void +update_modifier_state(struct weston_seat *seat, uint32_t serial, uint32_t key, + enum wl_keyboard_key_state state) +{ +} +#endif + +WL_EXPORT void +notify_key(struct weston_seat *seat, uint32_t time, uint32_t key, + enum wl_keyboard_key_state state, + enum weston_key_state_update update_state) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_keyboard *keyboard = seat->keyboard; + struct weston_surface *focus = + (struct weston_surface *) keyboard->focus; + struct weston_keyboard_grab *grab = keyboard->grab; + uint32_t serial = wl_display_next_serial(compositor->wl_display); + uint32_t *k, *end; + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + if (compositor->ping_handler && focus) + compositor->ping_handler(focus, serial); + + weston_compositor_idle_inhibit(compositor); + keyboard->grab_key = key; + keyboard->grab_time = time; + } else { + weston_compositor_idle_release(compositor); + } + + end = keyboard->keys.data + keyboard->keys.size; + for (k = keyboard->keys.data; k < end; k++) { + if (*k == key) { + /* Ignore server-generated repeats. */ + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) + return; + *k = *--end; + } + } + keyboard->keys.size = (void *) end - keyboard->keys.data; + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + k = wl_array_add(&keyboard->keys, sizeof *k); + *k = key; + } + + if (grab == &keyboard->default_grab || + grab == &keyboard->input_method_grab) { + weston_compositor_run_key_binding(compositor, seat, time, key, + state); + grab = keyboard->grab; + } + + grab->interface->key(grab, time, key, state); + + if (update_state == STATE_UPDATE_AUTOMATIC) { + update_modifier_state(seat, + wl_display_get_serial(compositor->wl_display), + key, + state); + } +} + +WL_EXPORT void +notify_pointer_focus(struct weston_seat *seat, struct weston_output *output, + wl_fixed_t x, wl_fixed_t y) +{ + struct weston_compositor *compositor = seat->compositor; + + if (output) { + move_pointer(seat, x, y); + compositor->focus = 1; + } else { + compositor->focus = 0; + /* FIXME: We should call weston_pointer_set_focus(seat, + * NULL) here, but somehow that breaks re-entry... */ + } +} + +static void +destroy_device_saved_kbd_focus(struct wl_listener *listener, void *data) +{ + struct weston_seat *ws; + + ws = container_of(listener, struct weston_seat, + saved_kbd_focus_listener); + + ws->saved_kbd_focus = NULL; +} + +WL_EXPORT void +notify_keyboard_focus_in(struct weston_seat *seat, struct wl_array *keys, + enum weston_key_state_update update_state) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_keyboard *keyboard = seat->keyboard; + struct weston_surface *surface; + uint32_t *k, serial; + + serial = wl_display_next_serial(compositor->wl_display); + wl_array_copy(&keyboard->keys, keys); + wl_array_for_each(k, &keyboard->keys) { + weston_compositor_idle_inhibit(compositor); + if (update_state == STATE_UPDATE_AUTOMATIC) + update_modifier_state(seat, serial, *k, + WL_KEYBOARD_KEY_STATE_PRESSED); + } + + /* Run key bindings after we've updated the state. */ + wl_array_for_each(k, &keyboard->keys) { + weston_compositor_run_key_binding(compositor, seat, 0, *k, + WL_KEYBOARD_KEY_STATE_PRESSED); + } + + surface = seat->saved_kbd_focus; + + if (surface) { + wl_list_remove(&seat->saved_kbd_focus_listener.link); + weston_keyboard_set_focus(keyboard, surface); + seat->saved_kbd_focus = NULL; + } +} + +WL_EXPORT void +notify_keyboard_focus_out(struct weston_seat *seat) +{ + struct weston_compositor *compositor = seat->compositor; + struct weston_keyboard *keyboard = seat->keyboard; + uint32_t *k, serial; + + serial = wl_display_next_serial(compositor->wl_display); + wl_array_for_each(k, &keyboard->keys) { + weston_compositor_idle_release(compositor); + update_modifier_state(seat, serial, *k, + WL_KEYBOARD_KEY_STATE_RELEASED); + } + + seat->modifier_state = 0; + + if (keyboard->focus) { + seat->saved_kbd_focus = keyboard->focus; + seat->saved_kbd_focus_listener.notify = + destroy_device_saved_kbd_focus; + wl_signal_add(&keyboard->focus->destroy_signal, + &seat->saved_kbd_focus_listener); + } + + weston_keyboard_set_focus(keyboard, NULL); + weston_keyboard_cancel_grab(keyboard); +} + +WL_EXPORT void +weston_touch_set_focus(struct weston_seat *seat, struct weston_surface *surface) +{ + struct wl_list *focus_resource_list; + + focus_resource_list = &seat->touch->focus_resource_list; + + if (seat->touch->focus == surface) + return; + + if (!wl_list_empty(focus_resource_list)) { + move_resources(&seat->touch->resource_list, + focus_resource_list); + } + + if (surface) { + struct wl_client *surface_client = + wl_resource_get_client(surface->resource); + move_resources_for_client(focus_resource_list, + &seat->touch->resource_list, + surface_client); + } + seat->touch->focus = surface; +} + +/** + * notify_touch - emulates button touches and notifies surfaces accordingly. + * + * It assumes always the correct cycle sequence until it gets here: touch_down + * → touch_update → ... → touch_update → touch_end. The driver is responsible + * for sending along such order. + * + */ +WL_EXPORT void +notify_touch(struct weston_seat *seat, uint32_t time, int touch_id, + wl_fixed_t x, wl_fixed_t y, int touch_type) +{ + struct weston_compositor *ec = seat->compositor; + struct weston_touch *touch = seat->touch; + struct weston_touch_grab *grab = touch->grab; + struct weston_surface *es; + wl_fixed_t sx, sy; + + /* Update grab's global coordinates. */ + if (touch_id == touch->grab_touch_id && touch_type != WL_TOUCH_UP) { + touch->grab_x = x; + touch->grab_y = y; + } + + switch (touch_type) { + case WL_TOUCH_DOWN: + weston_compositor_idle_inhibit(ec); + + seat->num_tp++; + + /* the first finger down picks the surface, and all further go + * to that surface for the remainder of the touch session i.e. + * until all touch points are up again. */ + if (seat->num_tp == 1) { + es = weston_compositor_pick_surface(ec, x, y, &sx, &sy); + weston_touch_set_focus(seat, es); + } else if (touch->focus) { + es = (struct weston_surface *) touch->focus; + weston_surface_from_global_fixed(es, x, y, &sx, &sy); + } else { + /* Unexpected condition: We have non-initial touch but + * there is no focused surface. + */ + weston_log("touch event received with %d points down" + "but no surface focused\n", seat->num_tp); + return; + } + + grab->interface->down(grab, time, touch_id, sx, sy); + if (seat->num_tp == 1) { + touch->grab_serial = + wl_display_get_serial(ec->wl_display); + touch->grab_touch_id = touch_id; + touch->grab_time = time; + touch->grab_x = x; + touch->grab_y = y; + } + + break; + case WL_TOUCH_MOTION: + es = (struct weston_surface *) touch->focus; + if (!es) + break; + + weston_surface_from_global_fixed(es, x, y, &sx, &sy); + grab->interface->motion(grab, time, touch_id, sx, sy); + break; + case WL_TOUCH_UP: + weston_compositor_idle_release(ec); + seat->num_tp--; + + grab->interface->up(grab, time, touch_id); + if (seat->num_tp == 0) + weston_touch_set_focus(seat, NULL); + break; + } + + weston_compositor_run_touch_binding(ec, seat, time, touch_type); +} + +static void +pointer_cursor_surface_configure(struct weston_surface *es, + int32_t dx, int32_t dy, int32_t width, int32_t height) +{ + struct weston_pointer *pointer = es->configure_private; + int x, y; + + if (width == 0) + return; + + assert(es == pointer->sprite); + + pointer->hotspot_x -= dx; + pointer->hotspot_y -= dy; + + x = wl_fixed_to_int(pointer->x) - pointer->hotspot_x; + y = wl_fixed_to_int(pointer->y) - pointer->hotspot_y; + + weston_surface_configure(pointer->sprite, x, y, width, height); + + empty_region(&es->pending.input); + + if (!weston_surface_is_mapped(es)) { + wl_list_insert(&es->compositor->cursor_layer.surface_list, + &es->layer_link); + weston_surface_update_transform(es); + } +} + +static void +pointer_set_cursor(struct wl_client *client, struct wl_resource *resource, + uint32_t serial, struct wl_resource *surface_resource, + int32_t x, int32_t y) +{ + struct weston_pointer *pointer = wl_resource_get_user_data(resource); + struct weston_surface *surface = NULL; + + if (surface_resource) + surface = wl_resource_get_user_data(surface_resource); + + if (pointer->focus == NULL) + return; + /* pointer->focus->resource can be NULL. Surfaces like the + black_surface used in shell.c for fullscreen don't have + a resource, but can still have focus */ + if (pointer->focus->resource == NULL) + return; + if (wl_resource_get_client(pointer->focus->resource) != client) + return; + if (pointer->focus_serial - serial > UINT32_MAX / 2) + return; + + if (surface && surface != pointer->sprite) { + if (surface->configure) { + wl_resource_post_error(surface->resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface->configure already " + "set"); + return; + } + } + + if (pointer->sprite) + pointer_unmap_sprite(pointer); + + if (!surface) + return; + + wl_signal_add(&surface->destroy_signal, + &pointer->sprite_destroy_listener); + + surface->configure = pointer_cursor_surface_configure; + surface->configure_private = pointer; + pointer->sprite = surface; + pointer->hotspot_x = x; + pointer->hotspot_y = y; + + if (surface->buffer_ref.buffer) + pointer_cursor_surface_configure(surface, 0, 0, weston_surface_buffer_width(surface), + weston_surface_buffer_height(surface)); +} + +static void +pointer_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_pointer_interface pointer_interface = { + pointer_set_cursor, + pointer_release +}; + +static void +seat_get_pointer(struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + struct weston_seat *seat = wl_resource_get_user_data(resource); + struct wl_resource *cr; + + if (!seat->pointer) + return; + + cr = wl_resource_create(client, &wl_pointer_interface, + wl_resource_get_version(resource), id); + if (cr == NULL) { + wl_client_post_no_memory(client); + return; + } + + /* May be moved to focused list later by either + * weston_pointer_set_focus or directly if this client is already + * focused */ + wl_list_insert(&seat->pointer->resource_list, wl_resource_get_link(cr)); + wl_resource_set_implementation(cr, &pointer_interface, seat->pointer, + unbind_resource); + + if (seat->pointer->focus && seat->pointer->focus->resource && + wl_resource_get_client(seat->pointer->focus->resource) == client) { + struct weston_surface *surface; + wl_fixed_t sx, sy; + + surface = (struct weston_surface *) seat->pointer->focus; + weston_surface_from_global_fixed(surface, + seat->pointer->x, + seat->pointer->y, + &sx, + &sy); + + wl_list_remove(wl_resource_get_link(cr)); + wl_list_insert(&seat->pointer->focus_resource_list, + wl_resource_get_link(cr)); + wl_pointer_send_enter(cr, + seat->pointer->focus_serial, + surface->resource, + sx, sy); + } +} + +static void +keyboard_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_keyboard_interface keyboard_interface = { + keyboard_release +}; + +static int +should_send_modifiers_to_client(struct weston_seat *seat, + struct wl_client *client) +{ + if (seat->keyboard && + seat->keyboard->focus && + wl_resource_get_client(seat->keyboard->focus->resource) == client) + return 1; + + if (seat->pointer && + seat->pointer->focus && + wl_resource_get_client(seat->pointer->focus->resource) == client) + return 1; + + return 0; +} + +static void +seat_get_keyboard(struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + struct weston_seat *seat = wl_resource_get_user_data(resource); + struct wl_resource *cr; + + if (!seat->keyboard) + return; + + cr = wl_resource_create(client, &wl_keyboard_interface, + wl_resource_get_version(resource), id); + if (cr == NULL) { + wl_client_post_no_memory(client); + return; + } + + /* May be moved to focused list later by either + * weston_keyboard_set_focus or directly if this client is already + * focused */ + wl_list_insert(&seat->keyboard->resource_list, wl_resource_get_link(cr)); + wl_resource_set_implementation(cr, &keyboard_interface, + seat, unbind_resource); + + if (seat->compositor->use_xkbcommon) { + wl_keyboard_send_keymap(cr, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, + seat->xkb_info->keymap_fd, + seat->xkb_info->keymap_size); + } else { + int null_fd = open("/dev/null", O_RDONLY); + wl_keyboard_send_keymap(cr, WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP, + null_fd, + 0); + close(null_fd); + } + + if (should_send_modifiers_to_client(seat, client)) { + send_modifiers_to_resource(seat->keyboard, + cr, + seat->keyboard->focus_serial); + } + + if (seat->keyboard->focus && + wl_resource_get_client(seat->keyboard->focus->resource) == client) { + struct weston_surface *surface = + (struct weston_surface *) seat->keyboard->focus; + + wl_list_remove(wl_resource_get_link(cr)); + wl_list_insert(&seat->keyboard->focus_resource_list, + wl_resource_get_link(cr)); + wl_keyboard_send_enter(cr, + seat->keyboard->focus_serial, + surface->resource, + &seat->keyboard->keys); + + /* If this is the first keyboard resource for this + * client... */ + if (seat->keyboard->focus_resource_list.prev == + wl_resource_get_link(cr)) + wl_data_device_set_keyboard_focus(seat); + } +} + +static void +touch_release(struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static const struct wl_touch_interface touch_interface = { + touch_release +}; + +static void +seat_get_touch(struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + struct weston_seat *seat = wl_resource_get_user_data(resource); + struct wl_resource *cr; + + if (!seat->touch) + return; + + cr = wl_resource_create(client, &wl_touch_interface, + wl_resource_get_version(resource), id); + if (cr == NULL) { + wl_client_post_no_memory(client); + return; + } + + if (seat->touch->focus && + wl_resource_get_client(seat->touch->focus->resource) == client) { + wl_list_insert(&seat->touch->resource_list, + wl_resource_get_link(cr)); + } else { + wl_list_insert(&seat->touch->focus_resource_list, + wl_resource_get_link(cr)); + } + wl_resource_set_implementation(cr, &touch_interface, + seat, unbind_resource); +} + +static const struct wl_seat_interface seat_interface = { + seat_get_pointer, + seat_get_keyboard, + seat_get_touch, +}; + +static void +bind_seat(struct wl_client *client, void *data, uint32_t version, uint32_t id) +{ + struct weston_seat *seat = data; + struct wl_resource *resource; + enum wl_seat_capability caps = 0; + + resource = wl_resource_create(client, + &wl_seat_interface, MIN(version, 3), id); + wl_list_insert(&seat->base_resource_list, wl_resource_get_link(resource)); + wl_resource_set_implementation(resource, &seat_interface, data, + unbind_resource); + + if (seat->pointer) + caps |= WL_SEAT_CAPABILITY_POINTER; + if (seat->keyboard) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + if (seat->touch) + caps |= WL_SEAT_CAPABILITY_TOUCH; + + wl_seat_send_capabilities(resource, caps); + if (version >= 2) + wl_seat_send_name(resource, seat->seat_name); +} + +#ifdef ENABLE_XKBCOMMON +int +weston_compositor_xkb_init(struct weston_compositor *ec, + struct xkb_rule_names *names) +{ + ec->use_xkbcommon = 1; + + if (ec->xkb_context == NULL) { + ec->xkb_context = xkb_context_new(0); + if (ec->xkb_context == NULL) { + weston_log("failed to create XKB context\n"); + return -1; + } + } + + if (names) + ec->xkb_names = *names; + if (!ec->xkb_names.rules) + ec->xkb_names.rules = strdup("evdev"); + if (!ec->xkb_names.model) + ec->xkb_names.model = strdup("pc105"); + if (!ec->xkb_names.layout) + ec->xkb_names.layout = strdup("us"); + + return 0; +} + +static void +weston_xkb_info_destroy(struct weston_xkb_info *xkb_info) +{ + if (--xkb_info->ref_count > 0) + return; + + if (xkb_info->keymap) + xkb_map_unref(xkb_info->keymap); + + if (xkb_info->keymap_area) + munmap(xkb_info->keymap_area, xkb_info->keymap_size); + if (xkb_info->keymap_fd >= 0) + close(xkb_info->keymap_fd); + free(xkb_info); +} + +void +weston_compositor_xkb_destroy(struct weston_compositor *ec) +{ + /* + * If we're operating in raw keyboard mode, we never initialized + * libxkbcommon so there's no cleanup to do either. + */ + if (!ec->use_xkbcommon) + return; + + free((char *) ec->xkb_names.rules); + free((char *) ec->xkb_names.model); + free((char *) ec->xkb_names.layout); + free((char *) ec->xkb_names.variant); + free((char *) ec->xkb_names.options); + + if (ec->xkb_info) + weston_xkb_info_destroy(ec->xkb_info); + xkb_context_unref(ec->xkb_context); +} + +static struct weston_xkb_info * +weston_xkb_info_create(struct xkb_keymap *keymap) +{ + struct weston_xkb_info *xkb_info = zalloc(sizeof *xkb_info); + if (xkb_info == NULL) + return NULL; + + xkb_info->keymap = xkb_map_ref(keymap); + xkb_info->ref_count = 1; + + char *keymap_str; + + xkb_info->shift_mod = xkb_map_mod_get_index(xkb_info->keymap, + XKB_MOD_NAME_SHIFT); + xkb_info->caps_mod = xkb_map_mod_get_index(xkb_info->keymap, + XKB_MOD_NAME_CAPS); + xkb_info->ctrl_mod = xkb_map_mod_get_index(xkb_info->keymap, + XKB_MOD_NAME_CTRL); + xkb_info->alt_mod = xkb_map_mod_get_index(xkb_info->keymap, + XKB_MOD_NAME_ALT); + xkb_info->mod2_mod = xkb_map_mod_get_index(xkb_info->keymap, "Mod2"); + xkb_info->mod3_mod = xkb_map_mod_get_index(xkb_info->keymap, "Mod3"); + xkb_info->super_mod = xkb_map_mod_get_index(xkb_info->keymap, + XKB_MOD_NAME_LOGO); + xkb_info->mod5_mod = xkb_map_mod_get_index(xkb_info->keymap, "Mod5"); + + xkb_info->num_led = xkb_map_led_get_index(xkb_info->keymap, + XKB_LED_NAME_NUM); + xkb_info->caps_led = xkb_map_led_get_index(xkb_info->keymap, + XKB_LED_NAME_CAPS); + xkb_info->scroll_led = xkb_map_led_get_index(xkb_info->keymap, + XKB_LED_NAME_SCROLL); + + keymap_str = xkb_map_get_as_string(xkb_info->keymap); + if (keymap_str == NULL) { + weston_log("failed to get string version of keymap\n"); + goto err_keymap; + } + xkb_info->keymap_size = strlen(keymap_str) + 1; + + xkb_info->keymap_fd = os_create_anonymous_file(xkb_info->keymap_size); + if (xkb_info->keymap_fd < 0) { + weston_log("creating a keymap file for %lu bytes failed: %m\n", + (unsigned long) xkb_info->keymap_size); + goto err_keymap_str; + } + + xkb_info->keymap_area = mmap(NULL, xkb_info->keymap_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, xkb_info->keymap_fd, 0); + if (xkb_info->keymap_area == MAP_FAILED) { + weston_log("failed to mmap() %lu bytes\n", + (unsigned long) xkb_info->keymap_size); + goto err_dev_zero; + } + strcpy(xkb_info->keymap_area, keymap_str); + free(keymap_str); + + return xkb_info; + +err_dev_zero: + close(xkb_info->keymap_fd); +err_keymap_str: + free(keymap_str); +err_keymap: + xkb_map_unref(xkb_info->keymap); + free(xkb_info); + return NULL; +} + +static int +weston_compositor_build_global_keymap(struct weston_compositor *ec) +{ + struct xkb_keymap *keymap; + + if (ec->xkb_info != NULL) + return 0; + + keymap = xkb_map_new_from_names(ec->xkb_context, + &ec->xkb_names, + 0); + if (keymap == NULL) { + weston_log("failed to compile global XKB keymap\n"); + weston_log(" tried rules %s, model %s, layout %s, variant %s, " + "options %s\n", + ec->xkb_names.rules, ec->xkb_names.model, + ec->xkb_names.layout, ec->xkb_names.variant, + ec->xkb_names.options); + return -1; + } + + ec->xkb_info = weston_xkb_info_create(keymap); + if (ec->xkb_info == NULL) + return -1; + + return 0; +} +#else +int +weston_compositor_xkb_init(struct weston_compositor *ec, + struct xkb_rule_names *names) +{ + return 0; +} + +void +weston_compositor_xkb_destroy(struct weston_compositor *ec) +{ +} +#endif + +WL_EXPORT int +weston_seat_init_keyboard(struct weston_seat *seat, struct xkb_keymap *keymap) +{ + struct weston_keyboard *keyboard; + + if (seat->keyboard) { + seat->keyboard_device_count += 1; + if (seat->keyboard_device_count == 1) + seat_send_updated_caps(seat); + return 0; + } + +#ifdef ENABLE_XKBCOMMON + if (seat->compositor->use_xkbcommon) { + if (keymap != NULL) { + seat->xkb_info = weston_xkb_info_create(keymap); + if (seat->xkb_info == NULL) + return -1; + } else { + if (weston_compositor_build_global_keymap(seat->compositor) < 0) + return -1; + seat->xkb_info = seat->compositor->xkb_info; + seat->xkb_info->ref_count++; + } + + seat->xkb_state.state = xkb_state_new(seat->xkb_info->keymap); + if (seat->xkb_state.state == NULL) { + weston_log("failed to initialise XKB state\n"); + return -1; + } + + seat->xkb_state.leds = 0; + } +#endif + + keyboard = weston_keyboard_create(); + if (keyboard == NULL) { + weston_log("failed to allocate weston keyboard struct\n"); + return -1; + } + + seat->keyboard = keyboard; + seat->keyboard_device_count = 1; + keyboard->seat = seat; + + seat_send_updated_caps(seat); + + return 0; +} + +WL_EXPORT void +weston_seat_release_keyboard(struct weston_seat *seat) +{ + seat->keyboard_device_count--; + if (seat->keyboard_device_count == 0) { + weston_keyboard_set_focus(seat->keyboard, NULL); + weston_keyboard_cancel_grab(seat->keyboard); + seat_send_updated_caps(seat); + } +} + +WL_EXPORT void +weston_seat_init_pointer(struct weston_seat *seat) +{ + struct weston_pointer *pointer; + + if (seat->pointer) { + seat->pointer_device_count += 1; + if (seat->pointer_device_count == 1) + seat_send_updated_caps(seat); + return; + } + + pointer = weston_pointer_create(); + if (pointer == NULL) + return; + + seat->pointer = pointer; + seat->pointer_device_count = 1; + pointer->seat = seat; + + seat_send_updated_caps(seat); +} + +WL_EXPORT void +weston_seat_release_pointer(struct weston_seat *seat) +{ + struct weston_pointer *pointer = seat->pointer; + + seat->pointer_device_count--; + if (seat->pointer_device_count == 0) { + weston_pointer_set_focus(pointer, NULL, + wl_fixed_from_int(0), + wl_fixed_from_int(0)); + weston_pointer_cancel_grab(pointer); + + if (pointer->sprite) + pointer_unmap_sprite(pointer); + + seat_send_updated_caps(seat); + } +} + +WL_EXPORT void +weston_seat_init_touch(struct weston_seat *seat) +{ + struct weston_touch *touch; + + if (seat->touch) { + seat->touch_device_count += 1; + if (seat->touch_device_count == 1) + seat_send_updated_caps(seat); + return; + } + + touch = weston_touch_create(); + if (touch == NULL) + return; + + seat->touch = touch; + seat->touch_device_count = 1; + touch->seat = seat; + + seat_send_updated_caps(seat); +} + +WL_EXPORT void +weston_seat_release_touch(struct weston_seat *seat) +{ + seat->touch_device_count--; + if (seat->touch_device_count == 0) { + weston_touch_set_focus(seat, NULL); + weston_touch_cancel_grab(seat->touch); + seat_send_updated_caps(seat); + } +} + +WL_EXPORT void +weston_seat_init(struct weston_seat *seat, struct weston_compositor *ec, + const char *seat_name) +{ + memset(seat, 0, sizeof *seat); + + seat->selection_data_source = NULL; + wl_list_init(&seat->base_resource_list); + wl_signal_init(&seat->selection_signal); + wl_list_init(&seat->drag_resource_list); + wl_signal_init(&seat->destroy_signal); + + seat->global = wl_global_create(ec->wl_display, &wl_seat_interface, 3, + seat, bind_seat); + + seat->compositor = ec; + seat->modifier_state = 0; + seat->num_tp = 0; + seat->seat_name = strdup(seat_name); + + wl_list_insert(ec->seat_list.prev, &seat->link); + + clipboard_create(seat); + + wl_signal_emit(&ec->seat_created_signal, seat); +} + +WL_EXPORT void +weston_seat_release(struct weston_seat *seat) +{ + wl_list_remove(&seat->link); + +#ifdef ENABLE_XKBCOMMON + if (seat->compositor->use_xkbcommon) { + if (seat->xkb_state.state != NULL) + xkb_state_unref(seat->xkb_state.state); + if (seat->xkb_info) + weston_xkb_info_destroy(seat->xkb_info); + } +#endif + + if (seat->pointer) + weston_pointer_destroy(seat->pointer); + if (seat->keyboard) + weston_keyboard_destroy(seat->keyboard); + if (seat->touch) + weston_touch_destroy(seat->touch); + + free (seat->seat_name); + + wl_global_destroy(seat->global); + + wl_signal_emit(&seat->destroy_signal, seat); +} diff --git a/src/launcher-util.c b/src/launcher-util.c new file mode 100644 index 00000000..4ce06c4f --- /dev/null +++ b/src/launcher-util.c @@ -0,0 +1,397 @@ +/* + * Copyright © 2012 Benjamin Franzke + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef BUILD_DRM_COMPOSITOR +#include +#endif + +#include "compositor.h" +#include "launcher-util.h" +#include "weston-launch.h" + +#define DRM_MAJOR 226 + +#ifndef KDSKBMUTE +#define KDSKBMUTE 0x4B51 +#endif + +union cmsg_data { unsigned char b[4]; int fd; }; + +struct weston_launcher { + struct weston_compositor *compositor; + int fd; + struct wl_event_source *source; + + int kb_mode, tty, drm_fd; + struct wl_event_source *vt_source; +}; + +#ifdef BUILD_DRM_COMPOSITOR +static int +drm_drop_master(int drm_fd) +{ + return drmDropMaster(drm_fd); +} +static int +drm_set_master(int drm_fd) +{ + return drmSetMaster(drm_fd); +} +static int +drm_is_master(int drm_fd) +{ + drm_magic_t magic; + + return drmGetMagic(drm_fd, &magic) == 0 && + drmAuthMagic(drm_fd, magic) == 0; +} +#else +static int drm_drop_master(int drm_fd) {return 0;} +static int drm_set_master(int drm_fd) {return 0;} +static int drm_is_master(int drm_fd) {return 1;} +#endif + +int +weston_launcher_open(struct weston_launcher *launcher, + const char *path, int flags) +{ + int n, fd, ret = -1; + struct msghdr msg; + struct cmsghdr *cmsg; + struct iovec iov; + union cmsg_data *data; + char control[CMSG_SPACE(sizeof data->fd)]; + ssize_t len; + struct weston_launcher_open *message; + struct stat s; + + if (launcher->fd == -1) { + fd = open(path, flags | O_CLOEXEC); + if (fd == -1) + return -1; + + if (fstat(fd, &s) == -1) { + close(fd); + return -1; + } + + if (major(s.st_rdev) == DRM_MAJOR) { + launcher->drm_fd = fd; + if (!drm_is_master(fd)) { + weston_log("drm fd not master\n"); + close(fd); + return -1; + } + } + + return fd; + } + + n = sizeof(*message) + strlen(path) + 1; + message = malloc(n); + if (!message) + return -1; + + message->header.opcode = WESTON_LAUNCHER_OPEN; + message->flags = flags; + strcpy(message->path, path); + + do { + len = send(launcher->fd, message, n, 0); + } while (len < 0 && errno == EINTR); + free(message); + + memset(&msg, 0, sizeof msg); + iov.iov_base = &ret; + iov.iov_len = sizeof ret; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof control; + + do { + len = recvmsg(launcher->fd, &msg, MSG_CMSG_CLOEXEC); + } while (len < 0 && errno == EINTR); + + if (len != sizeof ret || + ret < 0) + return -1; + + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg || + cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) { + fprintf(stderr, "invalid control message\n"); + return -1; + } + + data = (union cmsg_data *) CMSG_DATA(cmsg); + if (data->fd == -1) { + fprintf(stderr, "missing drm fd in socket request\n"); + return -1; + } + + return data->fd; +} + +void +weston_launcher_restore(struct weston_launcher *launcher) +{ + struct vt_mode mode = { 0 }; + + if (ioctl(launcher->tty, KDSKBMUTE, 0) && + ioctl(launcher->tty, KDSKBMODE, launcher->kb_mode)) + weston_log("failed to restore kb mode: %m\n"); + + if (ioctl(launcher->tty, KDSETMODE, KD_TEXT)) + weston_log("failed to set KD_TEXT mode on tty: %m\n"); + + /* We have to drop master before we switch the VT back in + * VT_AUTO, so we don't risk switching to a VT with another + * display server, that will then fail to set drm master. */ + drm_drop_master(launcher->drm_fd); + + mode.mode = VT_AUTO; + if (ioctl(launcher->tty, VT_SETMODE, &mode) < 0) + weston_log("could not reset vt handling\n"); +} + +static int +weston_launcher_data(int fd, uint32_t mask, void *data) +{ + struct weston_launcher *launcher = data; + int len, ret; + + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + weston_log("launcher socket closed, exiting\n"); + /* Normally the weston-launch will reset the tty, but + * in this case it died or something, so do it here so + * we don't end up with a stuck vt. */ + weston_launcher_restore(launcher); + exit(-1); + } + + do { + len = recv(launcher->fd, &ret, sizeof ret, 0); + } while (len < 0 && errno == EINTR); + + switch (ret) { + case WESTON_LAUNCHER_ACTIVATE: + launcher->compositor->session_active = 1; + wl_signal_emit(&launcher->compositor->session_signal, + launcher->compositor); + break; + case WESTON_LAUNCHER_DEACTIVATE: + launcher->compositor->session_active = 0; + wl_signal_emit(&launcher->compositor->session_signal, + launcher->compositor); + break; + default: + weston_log("unexpected event from weston-launch\n"); + break; + } + + return 1; +} + +static int +vt_handler(int signal_number, void *data) +{ + struct weston_launcher *launcher = data; + struct weston_compositor *compositor = launcher->compositor; + + if (compositor->session_active) { + compositor->session_active = 0; + wl_signal_emit(&compositor->session_signal, compositor); + drm_drop_master(launcher->drm_fd); + ioctl(launcher->tty, VT_RELDISP, 1); + } else { + ioctl(launcher->tty, VT_RELDISP, VT_ACKACQ); + drm_set_master(launcher->drm_fd); + compositor->session_active = 1; + wl_signal_emit(&compositor->session_signal, compositor); + } + + return 1; +} + +static int +setup_tty(struct weston_launcher *launcher, int tty) +{ + struct wl_event_loop *loop; + struct vt_mode mode = { 0 }; + struct stat buf; + char tty_device[32] =""; + int ret, kd_mode; + + if (tty == 0) { + launcher->tty = dup(tty); + if (launcher->tty == -1) { + weston_log("couldn't dup stdin: %m\n"); + return -1; + } + } else { + snprintf(tty_device, sizeof tty_device, "/dev/tty%d", tty); + launcher->tty = open(tty_device, O_RDWR | O_CLOEXEC); + if (launcher->tty == -1) { + weston_log("couldn't open tty %s: %m\n", tty_device); + return -1; + } + } + + if (fstat(launcher->tty, &buf) == -1 || + major(buf.st_rdev) != TTY_MAJOR || minor(buf.st_rdev) == 0) { + weston_log("%s not a vt\n", tty_device); + weston_log("if running weston from ssh, " + "use --tty to specify a tty\n"); + goto err_close; + } + + ret = ioctl(launcher->tty, KDGETMODE, &kd_mode); + if (ret) { + weston_log("failed to get VT mode: %m\n"); + return -1; + } + if (kd_mode != KD_TEXT) { + weston_log("%s is already in graphics mode, " + "is another display server running?\n", tty_device); + goto err_close; + } + + ioctl(launcher->tty, VT_ACTIVATE, minor(buf.st_rdev)); + ioctl(launcher->tty, VT_WAITACTIVE, minor(buf.st_rdev)); + + if (ioctl(launcher->tty, KDGKBMODE, &launcher->kb_mode)) { + weston_log("failed to read keyboard mode: %m\n"); + goto err_close; + } + + if (ioctl(launcher->tty, KDSKBMUTE, 1) && + ioctl(launcher->tty, KDSKBMODE, K_OFF)) { + weston_log("failed to set K_OFF keyboard mode: %m\n"); + goto err_close; + } + + ret = ioctl(launcher->tty, KDSETMODE, KD_GRAPHICS); + if (ret) { + weston_log("failed to set KD_GRAPHICS mode on tty: %m\n"); + goto err_close; + } + + mode.mode = VT_PROCESS; + mode.relsig = SIGUSR1; + mode.acqsig = SIGUSR1; + if (ioctl(launcher->tty, VT_SETMODE, &mode) < 0) { + weston_log("failed to take control of vt handling\n"); + goto err_close; + } + + loop = wl_display_get_event_loop(launcher->compositor->wl_display); + launcher->vt_source = + wl_event_loop_add_signal(loop, SIGUSR1, vt_handler, launcher); + if (!launcher->vt_source) + goto err_close; + + return 0; + + err_close: + close(launcher->tty); + return -1; +} + +int +weston_launcher_activate_vt(struct weston_launcher *launcher, int vt) +{ + return ioctl(launcher->tty, VT_ACTIVATE, vt); +} + +struct weston_launcher * +weston_launcher_connect(struct weston_compositor *compositor, int tty) +{ + struct weston_launcher *launcher; + struct wl_event_loop *loop; + + launcher = malloc(sizeof *launcher); + if (launcher == NULL) + return NULL; + + launcher->compositor = compositor; + launcher->drm_fd = -1; + launcher->fd = weston_environment_get_fd("WESTON_LAUNCHER_SOCK"); + if (launcher->fd != -1) { + launcher->tty = weston_environment_get_fd("WESTON_TTY_FD"); + loop = wl_display_get_event_loop(compositor->wl_display); + launcher->source = wl_event_loop_add_fd(loop, launcher->fd, + WL_EVENT_READABLE, + weston_launcher_data, + launcher); + if (launcher->source == NULL) { + free(launcher); + return NULL; + } + } else if (geteuid() == 0) { + if (setup_tty(launcher, tty) == -1) { + free(launcher); + return NULL; + } + } else { + free(launcher); + return NULL; + } + + return launcher; +} + +void +weston_launcher_destroy(struct weston_launcher *launcher) +{ + if (launcher->fd != -1) { + close(launcher->fd); + wl_event_source_remove(launcher->source); + } else { + weston_launcher_restore(launcher); + wl_event_source_remove(launcher->vt_source); + } + + close(launcher->tty); + free(launcher); +} diff --git a/src/launcher-util.h b/src/launcher-util.h new file mode 100644 index 00000000..3e7ceb59 --- /dev/null +++ b/src/launcher-util.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2012 Benjamin Franzke + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _WESTON_LAUNCHER_UTIL_H_ +#define _WESTON_LAUNCHER_UTIL_H_ + +#include "config.h" + +#include "compositor.h" + +struct weston_launcher; + +struct weston_launcher * +weston_launcher_connect(struct weston_compositor *compositor, int tty); + +void +weston_launcher_destroy(struct weston_launcher *launcher); + +int +weston_launcher_open(struct weston_launcher *launcher, + const char *path, int flags); + +int +weston_launcher_activate_vt(struct weston_launcher *launcher, int vt); + +void +weston_launcher_restore(struct weston_launcher *launcher); + +#endif diff --git a/src/libbacklight.c b/src/libbacklight.c new file mode 100644 index 00000000..b3acc63f --- /dev/null +++ b/src/libbacklight.c @@ -0,0 +1,309 @@ +/* + * libbacklight - userspace interface to Linux backlight control + * + * Copyright © 2012 Intel Corporation + * Copyright 2010 Red Hat + * + * 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. + * + * Authors: + * Matthew Garrett + * Tiago Vignatti + */ + +#include "config.h" + +#include "libbacklight.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static long backlight_get(struct backlight *backlight, char *node) +{ + char buffer[100]; + char *path; + int fd; + long value, ret; + + if (asprintf(&path, "%s/%s", backlight->path, node) < 0) + return -ENOMEM +; + fd = open(path, O_RDONLY); + if (fd < 0) { + ret = -1; + goto out; + } + + ret = read(fd, &buffer, sizeof(buffer)); + if (ret < 1) { + ret = -1; + goto out; + } + + value = strtol(buffer, NULL, 10); + ret = value; +out: + if (fd >= 0) + close(fd); + free(path); + return ret; +} + +long backlight_get_brightness(struct backlight *backlight) +{ + return backlight_get(backlight, "brightness"); +} + +long backlight_get_max_brightness(struct backlight *backlight) +{ + return backlight_get(backlight, "max_brightness"); +} + +long backlight_get_actual_brightness(struct backlight *backlight) +{ + return backlight_get(backlight, "actual_brightness"); +} + +long backlight_set_brightness(struct backlight *backlight, long brightness) +{ + char *path; + char *buffer = NULL; + int fd; + long ret; + + if (asprintf(&path, "%s/%s", backlight->path, "brightness") < 0) + return -ENOMEM; + + fd = open(path, O_RDWR); + if (fd < 0) { + ret = -1; + goto out; + } + + ret = read(fd, &buffer, sizeof(buffer)); + if (ret < 1) { + ret = -1; + goto out; + } + + if (asprintf(&buffer, "%ld", brightness) < 0) { + ret = -1; + goto out; + } + + ret = write(fd, buffer, strlen(buffer)); + if (ret < 0) { + ret = -1; + goto out; + } + + ret = backlight_get_brightness(backlight); + backlight->brightness = ret; +out: + free(buffer); + free(path); + if (fd >= 0) + close(fd); + return ret; +} + +void backlight_destroy(struct backlight *backlight) +{ + if (!backlight) + return; + + if (backlight->path) + free(backlight->path); + + free(backlight); +} + +struct backlight *backlight_init(struct udev_device *drm_device, + uint32_t connector_type) +{ + const char *syspath = NULL; + char *pci_name = NULL; + char *chosen_path = NULL; + char *path = NULL; + DIR *backlights = NULL; + struct dirent *entry; + enum backlight_type type = 0; + char buffer[100]; + struct backlight *backlight = NULL; + int ret; + + if (!drm_device) + return NULL; + + syspath = udev_device_get_syspath(drm_device); + if (!syspath) + return NULL; + + if (asprintf(&path, "%s/%s", syspath, "device") < 0) + return NULL; + + ret = readlink(path, buffer, sizeof(buffer) - 1); + free(path); + if (ret < 0) + return NULL; + + buffer[ret] = '\0'; + pci_name = basename(buffer); + + if (connector_type <= 0) + return NULL; + + backlights = opendir("/sys/class/backlight"); + if (!backlights) + return NULL; + + /* Find the "best" backlight for the device. Firmware + interfaces are preferred over platform interfaces are + preferred over raw interfaces. For raw interfaces we'll + check if the device ID in the form of pci match, while + for firmware interfaces we require the pci ID to + match. It's assumed that platform interfaces always match, + since we can't actually associate them with IDs. + + A further awkwardness is that, while it's theoretically + possible for an ACPI interface to include support for + changing the backlight of external devices, it's unlikely + to ever be done. It's effectively impossible for a platform + interface to do so. So if we get asked about anything that + isn't LVDS or eDP, we pretty much have to require that the + control be supplied via a raw interface */ + + while ((entry = readdir(backlights))) { + char *backlight_path; + char *parent; + enum backlight_type entry_type; + int fd; + + if (entry->d_name[0] == '.') + continue; + + if (asprintf(&backlight_path, "%s/%s", "/sys/class/backlight", + entry->d_name) < 0) + goto err; + + if (asprintf(&path, "%s/%s", backlight_path, "type") < 0) + goto err; + + fd = open(path, O_RDONLY); + + if (fd < 0) + goto out; + + ret = read (fd, &buffer, sizeof(buffer)); + close (fd); + + if (ret < 1) + goto out; + + buffer[ret] = '\0'; + + if (!strncmp(buffer, "raw\n", sizeof(buffer))) + entry_type = BACKLIGHT_RAW; + else if (!strncmp(buffer, "platform\n", sizeof(buffer))) + entry_type = BACKLIGHT_PLATFORM; + else if (!strncmp(buffer, "firmware\n", sizeof(buffer))) + entry_type = BACKLIGHT_FIRMWARE; + else + goto out; + + if (connector_type != DRM_MODE_CONNECTOR_LVDS && + connector_type != DRM_MODE_CONNECTOR_eDP) { + /* External displays are assumed to require + gpu control at the moment */ + if (entry_type != BACKLIGHT_RAW) + goto out; + } + + free (path); + + if (asprintf(&path, "%s/%s", backlight_path, "device") < 0) + goto err; + + ret = readlink(path, buffer, sizeof(buffer) - 1); + + if (ret < 0) + goto out; + + buffer[ret] = '\0'; + + parent = basename(buffer); + + /* Perform matching for raw and firmware backlights - + platform backlights have to be assumed to match */ + if (entry_type == BACKLIGHT_RAW || + entry_type == BACKLIGHT_FIRMWARE) { + if (!(pci_name && !strcmp(pci_name, parent))) + goto out; + } + + if (entry_type < type) + goto out; + + type = entry_type; + + if (chosen_path) + free(chosen_path); + chosen_path = strdup(backlight_path); + + out: + free(backlight_path); + free(path); + } + + if (!chosen_path) + goto err; + + backlight = malloc(sizeof(struct backlight)); + + if (!backlight) + goto err; + + backlight->path = chosen_path; + backlight->type = type; + + backlight->max_brightness = backlight_get_max_brightness(backlight); + if (backlight->max_brightness < 0) + goto err; + + backlight->brightness = backlight_get_actual_brightness(backlight); + if (backlight->brightness < 0) + goto err; + + closedir(backlights); + return backlight; +err: + closedir(backlights); + free (chosen_path); + free (backlight); + return NULL; +} diff --git a/src/libbacklight.h b/src/libbacklight.h new file mode 100644 index 00000000..0c326711 --- /dev/null +++ b/src/libbacklight.h @@ -0,0 +1,49 @@ +#ifndef LIBBACKLIGHT_H +#define LIBBACKLIGHT_H +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum backlight_type { + BACKLIGHT_RAW, + BACKLIGHT_PLATFORM, + BACKLIGHT_FIRMWARE +}; + +struct backlight { + char *path; + int max_brightness; + int brightness; + enum backlight_type type; +}; + +/* + * Find and set up a backlight for a valid udev connector device, i.e. one + * matching drm subsytem and with status of connected. + */ +struct backlight *backlight_init(struct udev_device *drm_device, + uint32_t connector_type); + +/* Free backlight resources */ +void backlight_destroy(struct backlight *backlight); + +/* Provide the maximum backlight value */ +long backlight_get_max_brightness(struct backlight *backlight); + +/* Provide the cached backlight value */ +long backlight_get_brightness(struct backlight *backlight); + +/* Provide the hardware backlight value */ +long backlight_get_actual_brightness(struct backlight *backlight); + +/* Set the backlight to a value between 0 and max */ +long backlight_set_brightness(struct backlight *backlight, long brightness); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBBACKLIGHT_H */ diff --git a/src/log.c b/src/log.c new file mode 100644 index 00000000..911b0c63 --- /dev/null +++ b/src/log.c @@ -0,0 +1,132 @@ +/* + * Copyright © 2012 Martin Minarik + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "compositor.h" + +static FILE *weston_logfile = NULL; + +static int cached_tm_mday = -1; + +static int weston_log_timestamp(void) +{ + struct timeval tv; + struct tm *brokendown_time; + char string[128]; + + gettimeofday(&tv, NULL); + + brokendown_time = localtime(&tv.tv_sec); + if (brokendown_time->tm_mday != cached_tm_mday) { + strftime(string, sizeof string, "%Y-%m-%d %Z", brokendown_time); + fprintf(weston_logfile, "Date: %s\n", string); + + cached_tm_mday = brokendown_time->tm_mday; + } + + strftime(string, sizeof string, "%H:%M:%S", brokendown_time); + + return fprintf(weston_logfile, "[%s.%03li] ", string, tv.tv_usec/1000); +} + +static void +custom_handler(const char *fmt, va_list arg) +{ + weston_log_timestamp(); + fprintf(weston_logfile, "libwayland: "); + vfprintf(weston_logfile, fmt, arg); +} + +void +weston_log_file_open(const char *filename) +{ + wl_log_set_handler_server(custom_handler); + + if (filename != NULL) + weston_logfile = fopen(filename, "a"); + + if (weston_logfile == NULL) + weston_logfile = stderr; + else + setvbuf(weston_logfile, NULL, _IOLBF, 256); +} + +void +weston_log_file_close() +{ + if ((weston_logfile != stderr) && (weston_logfile != NULL)) + fclose(weston_logfile); + weston_logfile = stderr; +} + +WL_EXPORT int +weston_vlog(const char *fmt, va_list ap) +{ + int l; + + l = weston_log_timestamp(); + l += vfprintf(weston_logfile, fmt, ap); + + return l; +} + +WL_EXPORT int +weston_log(const char *fmt, ...) +{ + int l; + va_list argp; + + va_start(argp, fmt); + l = weston_vlog(fmt, argp); + va_end(argp); + + return l; +} + +WL_EXPORT int +weston_vlog_continue(const char *fmt, va_list argp) +{ + return vfprintf(weston_logfile, fmt, argp); +} + +WL_EXPORT int +weston_log_continue(const char *fmt, ...) +{ + int l; + va_list argp; + + va_start(argp, fmt); + l = weston_vlog_continue(fmt, argp); + va_end(argp); + + return l; +} diff --git a/src/noop-renderer.c b/src/noop-renderer.c new file mode 100644 index 00000000..91659f58 --- /dev/null +++ b/src/noop-renderer.c @@ -0,0 +1,98 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include + +#include "compositor.h" + +static int +noop_renderer_read_pixels(struct weston_output *output, + pixman_format_code_t format, void *pixels, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height) +{ + return 0; +} + +static void +noop_renderer_repaint_output(struct weston_output *output, + pixman_region32_t *output_damage) +{ +} + +static void +noop_renderer_flush_damage(struct weston_surface *surface) +{ +} + +static void +noop_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) +{ +} + +static int +noop_renderer_create_surface(struct weston_surface *surface) +{ + return 0; +} + +static void +noop_renderer_surface_set_color(struct weston_surface *surface, + float red, float green, float blue, float alpha) +{ +} + +static void +noop_renderer_destroy_surface(struct weston_surface *surface) +{ +} + +static void +noop_renderer_destroy(struct weston_compositor *ec) +{ + free(ec->renderer); + ec->renderer = NULL; +} + +WL_EXPORT int +noop_renderer_init(struct weston_compositor *ec) +{ + struct weston_renderer *renderer; + + renderer = malloc(sizeof *renderer); + if (renderer == NULL) + return -1; + + renderer->read_pixels = noop_renderer_read_pixels; + renderer->repaint_output = noop_renderer_repaint_output; + renderer->flush_damage = noop_renderer_flush_damage; + renderer->attach = noop_renderer_attach; + renderer->create_surface = noop_renderer_create_surface; + renderer->surface_set_color = noop_renderer_surface_set_color; + renderer->destroy_surface = noop_renderer_destroy_surface; + renderer->destroy = noop_renderer_destroy; + ec->renderer = renderer; + + return 0; +} diff --git a/src/pixman-renderer.c b/src/pixman-renderer.c new file mode 100644 index 00000000..987c5393 --- /dev/null +++ b/src/pixman-renderer.c @@ -0,0 +1,753 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2013 Vasily Khoruzhick + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include "pixman-renderer.h" + +#include + +struct pixman_output_state { + void *shadow_buffer; + pixman_image_t *shadow_image; + pixman_image_t *hw_buffer; +}; + +struct pixman_surface_state { + pixman_image_t *image; + struct weston_buffer_reference buffer_ref; +}; + +struct pixman_renderer { + struct weston_renderer base; + int repaint_debug; + pixman_image_t *debug_color; +}; + +static inline struct pixman_output_state * +get_output_state(struct weston_output *output) +{ + return (struct pixman_output_state *)output->renderer_state; +} + +static inline struct pixman_surface_state * +get_surface_state(struct weston_surface *surface) +{ + return (struct pixman_surface_state *)surface->renderer_state; +} + +static inline struct pixman_renderer * +get_renderer(struct weston_compositor *ec) +{ + return (struct pixman_renderer *)ec->renderer; +} + +static int +pixman_renderer_read_pixels(struct weston_output *output, + pixman_format_code_t format, void *pixels, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height) +{ + struct pixman_output_state *po = get_output_state(output); + pixman_transform_t transform; + pixman_image_t *out_buf; + + if (!po->hw_buffer) { + errno = ENODEV; + return -1; + } + + out_buf = pixman_image_create_bits(format, + width, + height, + pixels, + (PIXMAN_FORMAT_BPP(format) / 8) * width); + + /* Caller expects vflipped source image */ + pixman_transform_init_translate(&transform, + pixman_int_to_fixed (x), + pixman_int_to_fixed (y - pixman_image_get_height (po->hw_buffer))); + pixman_transform_scale(&transform, NULL, + pixman_fixed_1, + pixman_fixed_minus_1); + pixman_image_set_transform(po->hw_buffer, &transform); + + pixman_image_composite32(PIXMAN_OP_SRC, + po->hw_buffer, /* src */ + NULL /* mask */, + out_buf, /* dest */ + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + pixman_image_get_width (po->hw_buffer), /* width */ + pixman_image_get_height (po->hw_buffer) /* height */); + pixman_image_set_transform(po->hw_buffer, NULL); + + pixman_image_unref(out_buf); + + return 0; +} + +static void +box_scale(pixman_box32_t *dst, int scale) +{ + dst->x1 *= scale; + dst->x2 *= scale; + dst->y1 *= scale; + dst->y2 *= scale; +} + +static void +scale_region (pixman_region32_t *region, int scale) +{ + pixman_box32_t *rects, *scaled_rects; + int nrects, i; + + if (scale != 1) { + rects = pixman_region32_rectangles(region, &nrects); + scaled_rects = calloc(nrects, sizeof(pixman_box32_t)); + + for (i = 0; i < nrects; i++) { + scaled_rects[i] = rects[i]; + box_scale(&scaled_rects[i], scale); + } + pixman_region32_clear(region); + + pixman_region32_init_rects (region, scaled_rects, nrects); + free (scaled_rects); + } +} + +static void +transform_region (pixman_region32_t *region, int width, int height, enum wl_output_transform transform) +{ + pixman_box32_t *rects, *transformed_rects; + int nrects, i; + + if (transform == WL_OUTPUT_TRANSFORM_NORMAL) + return; + + rects = pixman_region32_rectangles(region, &nrects); + transformed_rects = calloc(nrects, sizeof(pixman_box32_t)); + + for (i = 0; i < nrects; i++) { + switch (transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + transformed_rects[i].x1 = rects[i].x1; + transformed_rects[i].y1 = rects[i].y1; + transformed_rects[i].x2 = rects[i].x2; + transformed_rects[i].y2 = rects[i].y2; + break; + case WL_OUTPUT_TRANSFORM_90: + transformed_rects[i].x1 = height - rects[i].y2; + transformed_rects[i].y1 = rects[i].x1; + transformed_rects[i].x2 = height - rects[i].y1; + transformed_rects[i].y2 = rects[i].x2; + break; + case WL_OUTPUT_TRANSFORM_180: + transformed_rects[i].x1 = width - rects[i].x2; + transformed_rects[i].y1 = height - rects[i].y2; + transformed_rects[i].x2 = width - rects[i].x1; + transformed_rects[i].y2 = height - rects[i].y1; + break; + case WL_OUTPUT_TRANSFORM_270: + transformed_rects[i].x1 = rects[i].y1; + transformed_rects[i].y1 = width - rects[i].x2; + transformed_rects[i].x2 = rects[i].y2; + transformed_rects[i].y2 = width - rects[i].x1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + transformed_rects[i].x1 = width - rects[i].x2; + transformed_rects[i].y1 = rects[i].y1; + transformed_rects[i].x2 = width - rects[i].x1; + transformed_rects[i].y2 = rects[i].y2; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + transformed_rects[i].x1 = height - rects[i].y2; + transformed_rects[i].y1 = width - rects[i].x2; + transformed_rects[i].x2 = height - rects[i].y1; + transformed_rects[i].y2 = width - rects[i].x1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + transformed_rects[i].x1 = rects[i].x1; + transformed_rects[i].y1 = height - rects[i].y2; + transformed_rects[i].x2 = rects[i].x2; + transformed_rects[i].y2 = height - rects[i].y1; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + transformed_rects[i].x1 = rects[i].y1; + transformed_rects[i].y1 = rects[i].x1; + transformed_rects[i].x2 = rects[i].y2; + transformed_rects[i].y2 = rects[i].x2; + break; + } + } + pixman_region32_clear(region); + + pixman_region32_init_rects (region, transformed_rects, nrects); + free (transformed_rects); +} + +static void +region_global_to_output(struct weston_output *output, pixman_region32_t *region) +{ + pixman_region32_translate(region, -output->x, -output->y); + transform_region (region, output->width, output->height, output->transform); + scale_region (region, output->current_scale); +} + +#define D2F(v) pixman_double_to_fixed((double)v) + +static void +repaint_region(struct weston_surface *es, struct weston_output *output, + pixman_region32_t *region, pixman_region32_t *surf_region, + pixman_op_t pixman_op) +{ + struct pixman_renderer *pr = + (struct pixman_renderer *) output->compositor->renderer; + struct pixman_surface_state *ps = get_surface_state(es); + struct pixman_output_state *po = get_output_state(output); + pixman_region32_t final_region; + float surface_x, surface_y; + pixman_transform_t transform; + pixman_fixed_t fw, fh; + + /* The final region to be painted is the intersection of + * 'region' and 'surf_region'. However, 'region' is in the global + * coordinates, and 'surf_region' is in the surface-local + * coordinates + */ + pixman_region32_init(&final_region); + if (surf_region) { + pixman_region32_copy(&final_region, surf_region); + + /* Convert from surface to global coordinates */ + if (!es->transform.enabled) { + pixman_region32_translate(&final_region, es->geometry.x, es->geometry.y); + } else { + weston_surface_to_global_float(es, 0, 0, &surface_x, &surface_y); + pixman_region32_translate(&final_region, (int)surface_x, (int)surface_y); + } + + /* We need to paint the intersection */ + pixman_region32_intersect(&final_region, &final_region, region); + } else { + /* If there is no surface region, just use the global region */ + pixman_region32_copy(&final_region, region); + } + + /* Convert from global to output coord */ + region_global_to_output(output, &final_region); + + /* And clip to it */ + pixman_image_set_clip_region32 (po->shadow_image, &final_region); + + /* Set up the source transformation based on the surface + position, the output position/transform/scale and the client + specified buffer transform/scale */ + pixman_transform_init_identity(&transform); + pixman_transform_scale(&transform, NULL, + pixman_double_to_fixed ((double)1.0/output->current_scale), + pixman_double_to_fixed ((double)1.0/output->current_scale)); + + fw = pixman_int_to_fixed(output->width); + fh = pixman_int_to_fixed(output->height); + switch (output->transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_FLIPPED: + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + pixman_transform_rotate(&transform, NULL, 0, -pixman_fixed_1); + pixman_transform_translate(&transform, NULL, 0, fh); + break; + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + pixman_transform_rotate(&transform, NULL, -pixman_fixed_1, 0); + pixman_transform_translate(&transform, NULL, fw, fh); + break; + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + pixman_transform_rotate(&transform, NULL, 0, pixman_fixed_1); + pixman_transform_translate(&transform, NULL, fw, 0); + break; + } + + switch (output->transform) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + pixman_transform_scale(&transform, NULL, + pixman_int_to_fixed (-1), + pixman_int_to_fixed (1)); + pixman_transform_translate(&transform, NULL, fw, 0); + break; + } + + pixman_transform_translate(&transform, NULL, + pixman_double_to_fixed (output->x), + pixman_double_to_fixed (output->y)); + + if (es->transform.enabled) { + /* Pixman supports only 2D transform matrix, but Weston uses 3D, + * so we're omitting Z coordinate here + */ + pixman_transform_t surface_transform = {{ + { D2F(es->transform.matrix.d[0]), + D2F(es->transform.matrix.d[4]), + D2F(es->transform.matrix.d[12]), + }, + { D2F(es->transform.matrix.d[1]), + D2F(es->transform.matrix.d[5]), + D2F(es->transform.matrix.d[13]), + }, + { D2F(es->transform.matrix.d[3]), + D2F(es->transform.matrix.d[7]), + D2F(es->transform.matrix.d[15]), + } + }}; + + pixman_transform_invert(&surface_transform, &surface_transform); + pixman_transform_multiply (&transform, &surface_transform, &transform); + } else { + pixman_transform_translate(&transform, NULL, + pixman_double_to_fixed ((double)-es->geometry.x), + pixman_double_to_fixed ((double)-es->geometry.y)); + } + + + fw = pixman_int_to_fixed(es->geometry.width); + fh = pixman_int_to_fixed(es->geometry.height); + + switch (es->buffer_transform) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + pixman_transform_scale(&transform, NULL, + pixman_int_to_fixed (-1), + pixman_int_to_fixed (1)); + pixman_transform_translate(&transform, NULL, fw, 0); + break; + } + + switch (es->buffer_transform) { + default: + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_FLIPPED: + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + pixman_transform_rotate(&transform, NULL, 0, pixman_fixed_1); + pixman_transform_translate(&transform, NULL, fh, 0); + break; + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + pixman_transform_rotate(&transform, NULL, -pixman_fixed_1, 0); + pixman_transform_translate(&transform, NULL, fw, fh); + break; + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + pixman_transform_rotate(&transform, NULL, 0, -pixman_fixed_1); + pixman_transform_translate(&transform, NULL, 0, fw); + break; + } + + pixman_transform_scale(&transform, NULL, + pixman_double_to_fixed ((double)es->buffer_scale), + pixman_double_to_fixed ((double)es->buffer_scale)); + + pixman_image_set_transform(ps->image, &transform); + + if (es->transform.enabled || output->current_scale != es->buffer_scale) + pixman_image_set_filter(ps->image, PIXMAN_FILTER_BILINEAR, NULL, 0); + else + pixman_image_set_filter(ps->image, PIXMAN_FILTER_NEAREST, NULL, 0); + + pixman_image_composite32(pixman_op, + ps->image, /* src */ + NULL /* mask */, + po->shadow_image, /* dest */ + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + pixman_image_get_width (po->shadow_image), /* width */ + pixman_image_get_height (po->shadow_image) /* height */); + + if (pr->repaint_debug) + pixman_image_composite32(PIXMAN_OP_OVER, + pr->debug_color, /* src */ + NULL /* mask */, + po->shadow_image, /* dest */ + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + pixman_image_get_width (po->shadow_image), /* width */ + pixman_image_get_height (po->shadow_image) /* height */); + + pixman_image_set_clip_region32 (po->shadow_image, NULL); + + pixman_region32_fini(&final_region); +} + +static void +draw_surface(struct weston_surface *es, struct weston_output *output, + pixman_region32_t *damage) /* in global coordinates */ +{ + struct pixman_surface_state *ps = get_surface_state(es); + /* repaint bounding region in global coordinates: */ + pixman_region32_t repaint; + /* non-opaque region in surface coordinates: */ + pixman_region32_t surface_blend; + + /* No buffer attached */ + if (!ps->image) + return; + + pixman_region32_init(&repaint); + pixman_region32_intersect(&repaint, + &es->transform.boundingbox, damage); + pixman_region32_subtract(&repaint, &repaint, &es->clip); + + if (!pixman_region32_not_empty(&repaint)) + goto out; + + if (output->zoom.active) { + weston_log("pixman renderer does not support zoom\n"); + goto out; + } + + /* TODO: Implement repaint_region_complex() using pixman_composite_trapezoids() */ + if (es->transform.enabled && + es->transform.matrix.type != WESTON_MATRIX_TRANSFORM_TRANSLATE) { + repaint_region(es, output, &repaint, NULL, PIXMAN_OP_OVER); + } else { + /* blended region is whole surface minus opaque region: */ + pixman_region32_init_rect(&surface_blend, 0, 0, + es->geometry.width, es->geometry.height); + pixman_region32_subtract(&surface_blend, &surface_blend, &es->opaque); + + if (pixman_region32_not_empty(&es->opaque)) { + repaint_region(es, output, &repaint, &es->opaque, PIXMAN_OP_SRC); + } + + if (pixman_region32_not_empty(&surface_blend)) { + repaint_region(es, output, &repaint, &surface_blend, PIXMAN_OP_OVER); + } + pixman_region32_fini(&surface_blend); + } + + +out: + pixman_region32_fini(&repaint); +} +static void +repaint_surfaces(struct weston_output *output, pixman_region32_t *damage) +{ + struct weston_compositor *compositor = output->compositor; + struct weston_surface *surface; + + wl_list_for_each_reverse(surface, &compositor->surface_list, link) + if (surface->plane == &compositor->primary_plane) + draw_surface(surface, output, damage); +} + +static void +copy_to_hw_buffer(struct weston_output *output, pixman_region32_t *region) +{ + struct pixman_output_state *po = get_output_state(output); + pixman_region32_t output_region; + + pixman_region32_init(&output_region); + pixman_region32_copy(&output_region, region); + + region_global_to_output(output, &output_region); + + pixman_image_set_clip_region32 (po->hw_buffer, &output_region); + + pixman_image_composite32(PIXMAN_OP_SRC, + po->shadow_image, /* src */ + NULL /* mask */, + po->hw_buffer, /* dest */ + 0, 0, /* src_x, src_y */ + 0, 0, /* mask_x, mask_y */ + 0, 0, /* dest_x, dest_y */ + pixman_image_get_width (po->hw_buffer), /* width */ + pixman_image_get_height (po->hw_buffer) /* height */); + + pixman_image_set_clip_region32 (po->hw_buffer, NULL); +} + +static void +pixman_renderer_repaint_output(struct weston_output *output, + pixman_region32_t *output_damage) +{ + struct pixman_output_state *po = get_output_state(output); + + if (!po->hw_buffer) + return; + + repaint_surfaces(output, output_damage); + copy_to_hw_buffer(output, output_damage); + + pixman_region32_copy(&output->previous_damage, output_damage); + wl_signal_emit(&output->frame_signal, output); + + /* Actual flip should be done by caller */ +} + +static void +pixman_renderer_flush_damage(struct weston_surface *surface) +{ + /* No-op for pixman renderer */ +} + +static void +pixman_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer) +{ + struct pixman_surface_state *ps = get_surface_state(es); + struct wl_shm_buffer *shm_buffer; + pixman_format_code_t pixman_format; + + weston_buffer_reference(&ps->buffer_ref, buffer); + + if (ps->image) { + pixman_image_unref(ps->image); + ps->image = NULL; + } + + if (!buffer) + return; + + shm_buffer = wl_shm_buffer_get(buffer->resource); + + if (! shm_buffer) { + weston_log("Pixman renderer supports only SHM buffers\n"); + weston_buffer_reference(&ps->buffer_ref, NULL); + return; + } + + switch (wl_shm_buffer_get_format(shm_buffer)) { + case WL_SHM_FORMAT_XRGB8888: + pixman_format = PIXMAN_x8r8g8b8; + break; + case WL_SHM_FORMAT_ARGB8888: + pixman_format = PIXMAN_a8r8g8b8; + break; + case WL_SHM_FORMAT_RGB565: + pixman_format = PIXMAN_r5g6b5; + break; + default: + weston_log("Unsupported SHM buffer format\n"); + weston_buffer_reference(&ps->buffer_ref, NULL); + return; + break; + } + + buffer->shm_buffer = shm_buffer; + buffer->width = wl_shm_buffer_get_width(shm_buffer); + buffer->height = wl_shm_buffer_get_height(shm_buffer); + + ps->image = pixman_image_create_bits(pixman_format, + buffer->width, buffer->height, + wl_shm_buffer_get_data(shm_buffer), + wl_shm_buffer_get_stride(shm_buffer)); +} + +static int +pixman_renderer_create_surface(struct weston_surface *surface) +{ + struct pixman_surface_state *ps; + + ps = calloc(1, sizeof *ps); + if (!ps) + return -1; + + surface->renderer_state = ps; + + return 0; +} + +static void +pixman_renderer_surface_set_color(struct weston_surface *es, + float red, float green, float blue, float alpha) +{ + struct pixman_surface_state *ps = get_surface_state(es); + pixman_color_t color; + + color.red = red * 0xffff; + color.green = green * 0xffff; + color.blue = blue * 0xffff; + color.alpha = alpha * 0xffff; + + if (ps->image) { + pixman_image_unref(ps->image); + ps->image = NULL; + } + + ps->image = pixman_image_create_solid_fill(&color); +} + +static void +pixman_renderer_destroy_surface(struct weston_surface *surface) +{ + struct pixman_surface_state *ps = get_surface_state(surface); + + if (ps->image) { + pixman_image_unref(ps->image); + ps->image = NULL; + } + weston_buffer_reference(&ps->buffer_ref, NULL); + free(ps); +} + +static void +pixman_renderer_destroy(struct weston_compositor *ec) +{ + free(ec->renderer); + ec->renderer = NULL; +} + +static void +debug_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + struct weston_compositor *ec = data; + struct pixman_renderer *pr = (struct pixman_renderer *) ec->renderer; + + pr->repaint_debug ^= 1; + + if (pr->repaint_debug) { + pixman_color_t red = { + 0x3fff, 0x0000, 0x0000, 0x3fff + }; + + pr->debug_color = pixman_image_create_solid_fill(&red); + } else { + pixman_image_unref(pr->debug_color); + weston_compositor_damage_all(ec); + } +} + +WL_EXPORT int +pixman_renderer_init(struct weston_compositor *ec) +{ + struct pixman_renderer *renderer; + + renderer = malloc(sizeof *renderer); + if (renderer == NULL) + return -1; + + renderer->repaint_debug = 0; + renderer->debug_color = NULL; + renderer->base.read_pixels = pixman_renderer_read_pixels; + renderer->base.repaint_output = pixman_renderer_repaint_output; + renderer->base.flush_damage = pixman_renderer_flush_damage; + renderer->base.attach = pixman_renderer_attach; + renderer->base.create_surface = pixman_renderer_create_surface; + renderer->base.surface_set_color = pixman_renderer_surface_set_color; + renderer->base.destroy_surface = pixman_renderer_destroy_surface; + renderer->base.destroy = pixman_renderer_destroy; + ec->renderer = &renderer->base; + ec->capabilities |= WESTON_CAP_ROTATION_ANY; + ec->capabilities |= WESTON_CAP_CAPTURE_YFLIP; + + weston_compositor_add_debug_binding(ec, KEY_R, + debug_binding, ec); + + wl_display_add_shm_format(ec->wl_display, WL_SHM_FORMAT_RGB565); + + return 0; +} + +WL_EXPORT void +pixman_renderer_output_set_buffer(struct weston_output *output, pixman_image_t *buffer) +{ + struct pixman_output_state *po = get_output_state(output); + + if (po->hw_buffer) + pixman_image_unref(po->hw_buffer); + po->hw_buffer = buffer; + + if (po->hw_buffer) { + output->compositor->read_format = pixman_image_get_format(po->hw_buffer); + pixman_image_ref(po->hw_buffer); + } +} + +WL_EXPORT int +pixman_renderer_output_create(struct weston_output *output) +{ + struct pixman_output_state *po = calloc(1, sizeof *po); + int w, h; + + if (!po) + return -1; + + /* set shadow image transformation */ + w = output->current_mode->width; + h = output->current_mode->height; + + po->shadow_buffer = malloc(w * h * 4); + + if (!po->shadow_buffer) { + free(po); + return -1; + } + + po->shadow_image = + pixman_image_create_bits(PIXMAN_x8r8g8b8, w, h, + po->shadow_buffer, w * 4); + + if (!po->shadow_image) { + free(po->shadow_buffer); + free(po); + return -1; + } + + output->renderer_state = po; + + return 0; +} + +WL_EXPORT void +pixman_renderer_output_destroy(struct weston_output *output) +{ + struct pixman_output_state *po = get_output_state(output); + + pixman_image_unref(po->shadow_image); + + if (po->hw_buffer) + pixman_image_unref(po->hw_buffer); + + po->shadow_image = NULL; + po->hw_buffer = NULL; + + free(po); +} diff --git a/src/pixman-renderer.h b/src/pixman-renderer.h new file mode 100644 index 00000000..2532299e --- /dev/null +++ b/src/pixman-renderer.h @@ -0,0 +1,37 @@ +/* + * Copyright © 2013 Vasily Khoruzhick + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include "compositor.h" + +int +pixman_renderer_init(struct weston_compositor *ec); + +int +pixman_renderer_output_create(struct weston_output *output); + +void +pixman_renderer_output_set_buffer(struct weston_output *output, pixman_image_t *buffer); + +void +pixman_renderer_output_destroy(struct weston_output *output); diff --git a/src/rpi-bcm-stubs.h b/src/rpi-bcm-stubs.h new file mode 100644 index 00000000..703cd771 --- /dev/null +++ b/src/rpi-bcm-stubs.h @@ -0,0 +1,314 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + * This file provides just enough types and stubs, so that the rpi-backend + * can be built without the real headers and libraries of the Raspberry Pi. + * + * This file CANNOT be used to build a working rpi-backend, it is intended + * only for build-testing, when the proper headers are not available. + */ + +#ifndef RPI_BCM_STUBS +#define RPI_BCM_STUBS + +#include + +/* from /opt/vc/include/bcm_host.h */ + +static inline void bcm_host_init(void) {} +static inline void bcm_host_deinit(void) {} + + +/* from /opt/vc/include/interface/vmcs_host/vc_dispservice_defs.h */ + +#define TRANSFORM_HFLIP (1<<0) +#define TRANSFORM_VFLIP (1<<1) +#define TRANSFORM_TRANSPOSE (1<<2) + + +/* from /opt/vc/include/interface/vctypes/vc_display_types.h */ + +typedef enum +{ + VCOS_DISPLAY_INPUT_FORMAT_INVALID = 0, +} DISPLAY_INPUT_FORMAT_T; + +/* from /opt/vc/include/interface/vctypes/vc_image_types.h */ + +typedef struct tag_VC_RECT_T { + int32_t x; + int32_t y; + int32_t width; + int32_t height; +} VC_RECT_T; + +typedef enum { + VC_IMAGE_ROT0, + /* these are not the right values: */ + VC_IMAGE_ROT90, + VC_IMAGE_ROT180, + VC_IMAGE_ROT270, + VC_IMAGE_MIRROR_ROT0, + VC_IMAGE_MIRROR_ROT90, + VC_IMAGE_MIRROR_ROT180, + VC_IMAGE_MIRROR_ROT270, +} VC_IMAGE_TRANSFORM_T; + +typedef enum +{ + VC_IMAGE_MIN = 0, + /* these are not the right values: */ + VC_IMAGE_ARGB8888, + VC_IMAGE_XRGB8888, + VC_IMAGE_RGB565, +} VC_IMAGE_TYPE_T; + +/* from /opt/vc/include/interface/vmcs_host/vc_dispmanx_types.h */ + +typedef uint32_t DISPMANX_DISPLAY_HANDLE_T; +typedef uint32_t DISPMANX_UPDATE_HANDLE_T; +typedef uint32_t DISPMANX_ELEMENT_HANDLE_T; +typedef uint32_t DISPMANX_RESOURCE_HANDLE_T; +typedef uint32_t DISPMANX_PROTECTION_T; + +#define DISPMANX_NO_HANDLE 0 +#define DISPMANX_PROTECTION_NONE 0 +#define DISPMANX_ID_HDMI 2 + +typedef enum { + /* Bottom 2 bits sets the alpha mode */ + DISPMANX_FLAGS_ALPHA_FROM_SOURCE = 0, + DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS = 1, + DISPMANX_FLAGS_ALPHA_FIXED_NON_ZERO = 2, + DISPMANX_FLAGS_ALPHA_FIXED_EXCEED_0X07 = 3, + + DISPMANX_FLAGS_ALPHA_PREMULT = 1 << 16, + DISPMANX_FLAGS_ALPHA_MIX = 1 << 17 +} DISPMANX_FLAGS_ALPHA_T; + +typedef struct { + DISPMANX_FLAGS_ALPHA_T flags; + uint32_t opacity; + DISPMANX_RESOURCE_HANDLE_T mask; +} VC_DISPMANX_ALPHA_T; + +typedef struct { + int32_t width; + int32_t height; + VC_IMAGE_TRANSFORM_T transform; + DISPLAY_INPUT_FORMAT_T input_format; +} DISPMANX_MODEINFO_T; + +typedef enum { + DISPMANX_NO_ROTATE = 0, + DISPMANX_ROTATE_90 = 1, + DISPMANX_ROTATE_180 = 2, + DISPMANX_ROTATE_270 = 3, + + DISPMANX_FLIP_HRIZ = 1 << 16, + DISPMANX_FLIP_VERT = 1 << 17 +} DISPMANX_TRANSFORM_T; + +typedef struct { + uint32_t dummy; +} DISPMANX_CLAMP_T; + +typedef void (*DISPMANX_CALLBACK_FUNC_T)(DISPMANX_UPDATE_HANDLE_T u, + void *arg); + +/* from /opt/vc/include/interface/vmcs_host/vc_dispmanx.h */ + +static inline int +vc_dispmanx_rect_set(VC_RECT_T *rect, uint32_t x_offset, uint32_t y_offset, + uint32_t width, uint32_t height) +{ + rect->x = x_offset; + rect->y = y_offset; + rect->width = width; + rect->height = height; + return 0; +} + +static inline DISPMANX_RESOURCE_HANDLE_T +vc_dispmanx_resource_create(VC_IMAGE_TYPE_T type, uint32_t width, + uint32_t height, uint32_t *native_image_handle) +{ + return DISPMANX_NO_HANDLE; +} + +static inline int +vc_dispmanx_resource_write_data(DISPMANX_RESOURCE_HANDLE_T res, + VC_IMAGE_TYPE_T src_type, + int src_pitch, + void *src_address, + const VC_RECT_T *rect) +{ + return -1; +} + +static inline int +vc_dispmanx_resource_write_data_rect(DISPMANX_RESOURCE_HANDLE_T handle, + VC_IMAGE_TYPE_T src_type, + int src_pitch, + void *src_address, + const VC_RECT_T *src_rect, + uint32_t dst_x, + uint32_t dst_y) +{ + return -1; +} + +static inline int +vc_dispmanx_resource_read_data(DISPMANX_RESOURCE_HANDLE_T handle, + const VC_RECT_T *p_rect, + void *dst_address, + uint32_t dst_pitch) +{ + return -1; +} + +static inline int +vc_dispmanx_resource_delete(DISPMANX_RESOURCE_HANDLE_T res) +{ + return -1; +} + +static inline DISPMANX_DISPLAY_HANDLE_T +vc_dispmanx_display_open(uint32_t device) +{ + return DISPMANX_NO_HANDLE; +} + +static inline int +vc_dispmanx_display_close(DISPMANX_DISPLAY_HANDLE_T display) +{ + return -1; +} + +static inline int +vc_dispmanx_display_get_info(DISPMANX_DISPLAY_HANDLE_T display, + DISPMANX_MODEINFO_T *pinfo) +{ + return -1; +} + +static inline DISPMANX_UPDATE_HANDLE_T +vc_dispmanx_update_start(int32_t priority) +{ + return DISPMANX_NO_HANDLE; +} + +static inline DISPMANX_ELEMENT_HANDLE_T +vc_dispmanx_element_add(DISPMANX_UPDATE_HANDLE_T update, + DISPMANX_DISPLAY_HANDLE_T display, + int32_t layer, + const VC_RECT_T *dest_rect, + DISPMANX_RESOURCE_HANDLE_T src, + const VC_RECT_T *src_rect, + DISPMANX_PROTECTION_T protection, + VC_DISPMANX_ALPHA_T *alpha, + DISPMANX_CLAMP_T *clamp, + DISPMANX_TRANSFORM_T transform) +{ + return DISPMANX_NO_HANDLE; +} + +static inline int +vc_dispmanx_element_change_source(DISPMANX_UPDATE_HANDLE_T update, + DISPMANX_ELEMENT_HANDLE_T element, + DISPMANX_RESOURCE_HANDLE_T src) +{ + return -1; +} + +static inline int +vc_dispmanx_element_modified(DISPMANX_UPDATE_HANDLE_T update, + DISPMANX_ELEMENT_HANDLE_T element, + const VC_RECT_T *rect) +{ + return -1; +} + +static inline int +vc_dispmanx_element_remove(DISPMANX_UPDATE_HANDLE_T update, + DISPMANX_ELEMENT_HANDLE_T element) +{ + return -1; +} + +static inline int +vc_dispmanx_update_submit(DISPMANX_UPDATE_HANDLE_T update, + DISPMANX_CALLBACK_FUNC_T cb_func, void *cb_arg) +{ + return -1; +} + +static inline int +vc_dispmanx_update_submit_sync(DISPMANX_UPDATE_HANDLE_T update) +{ + return -1; +} + +static inline int +vc_dispmanx_element_change_attributes(DISPMANX_UPDATE_HANDLE_T update, + DISPMANX_ELEMENT_HANDLE_T element, + uint32_t change_flags, + int32_t layer, + uint8_t opacity, + const VC_RECT_T *dest_rect, + const VC_RECT_T *src_rect, + DISPMANX_RESOURCE_HANDLE_T mask, + VC_IMAGE_TRANSFORM_T transform) +{ + return -1; +} + +static inline int +vc_dispmanx_snapshot(DISPMANX_DISPLAY_HANDLE_T display, + DISPMANX_RESOURCE_HANDLE_T snapshot_resource, + VC_IMAGE_TRANSFORM_T transform) +{ + return -1; +} + +struct wl_resource; +static inline DISPMANX_RESOURCE_HANDLE_T +vc_dispmanx_get_handle_from_wl_buffer(struct wl_resource *_buffer) +{ + return DISPMANX_NO_HANDLE; +} + +/* from /opt/vc/include/EGL/eglplatform.h */ + +typedef struct { + DISPMANX_ELEMENT_HANDLE_T element; + int width; + int height; +} EGL_DISPMANX_WINDOW_T; + +#endif /* RPI_BCM_STUBS */ diff --git a/src/rpi-renderer.c b/src/rpi-renderer.c new file mode 100644 index 00000000..a95cc604 --- /dev/null +++ b/src/rpi-renderer.c @@ -0,0 +1,1594 @@ +/* + * Copyright © 2012-2013 Raspberry Pi Foundation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_BCM_HOST +# include +#else +# include "rpi-bcm-stubs.h" +#endif + +#include "compositor.h" +#include "rpi-renderer.h" + +#ifdef ENABLE_EGL +#include +#include +#include "weston-egl-ext.h" +#endif + +/* + * Dispmanx API offers alpha-blended overlays for hardware compositing. + * The final composite consists of dispmanx elements, and their contents: + * the dispmanx resource assigned to the element. The elements may be + * scanned out directly, or composited to a temporary surface, depending on + * how the firmware decides to handle the scene. Updates to multiple elements + * may be queued in a single dispmanx update object, resulting in atomic and + * vblank synchronized display updates. + * + * To avoid tearing and display artifacts, the current dispmanx resource in a + * dispmanx element must not be touched. Therefore each element must be + * double-buffered, using two resources, the front and the back. While a + * dispmanx update is running, the both resources must be considered in use. + * + * A resource may be destroyed only, when the update removing the element has + * completed. Otherwise you risk showing an incomplete composition. + */ + +#ifndef ELEMENT_CHANGE_LAYER +/* copied from interface/vmcs_host/vc_vchi_dispmanx.h of userland.git */ +#define ELEMENT_CHANGE_LAYER (1<<0) +#define ELEMENT_CHANGE_OPACITY (1<<1) +#define ELEMENT_CHANGE_DEST_RECT (1<<2) +#define ELEMENT_CHANGE_SRC_RECT (1<<3) +#define ELEMENT_CHANGE_MASK_RESOURCE (1<<4) +#define ELEMENT_CHANGE_TRANSFORM (1<<5) +#endif + +#if 0 +#define DBG(...) \ + weston_log(__VA_ARGS__) +#else +#define DBG(...) do {} while (0) +#endif + +/* If we had a fully featured vc_dispmanx_resource_write_data()... */ +/*#define HAVE_RESOURCE_WRITE_DATA_RECT 1*/ + +struct rpi_resource { + DISPMANX_RESOURCE_HANDLE_T handle; + int width; + int height; /* height of the image (valid pixel data) */ + int stride; /* bytes */ + int buffer_height; /* height of the buffer */ + VC_IMAGE_TYPE_T ifmt; +}; + +struct rpir_output; + +struct rpir_egl_buffer { + struct weston_buffer_reference buffer_ref; + DISPMANX_RESOURCE_HANDLE_T resource_handle; +}; + +enum buffer_type { + BUFFER_TYPE_NULL, + BUFFER_TYPE_SHM, + BUFFER_TYPE_EGL +}; + +struct rpir_surface { + struct weston_surface *surface; + + /* If link is empty, the surface is guaranteed to not be on screen, + * i.e. updates removing Elements have completed. + */ + struct wl_list link; + + DISPMANX_ELEMENT_HANDLE_T handle; + int layer; + int need_swap; + int single_buffer; + + struct rpi_resource resources[2]; + struct rpi_resource *front; + struct rpi_resource *back; + pixman_region32_t prev_damage; + + struct rpir_egl_buffer *egl_front; + struct rpir_egl_buffer *egl_back; + struct rpir_egl_buffer *egl_old_front; + + struct weston_buffer_reference buffer_ref; + enum buffer_type buffer_type; +}; + +struct rpir_output { + DISPMANX_DISPLAY_HANDLE_T display; + + DISPMANX_UPDATE_HANDLE_T update; + struct weston_matrix matrix; + + /* all Elements currently on screen */ + struct wl_list surface_list; /* struct rpir_surface::link */ + + /* Elements just removed, waiting for update completion */ + struct wl_list surface_cleanup_list; /* struct rpir_surface::link */ + + struct rpi_resource capture_buffer; + uint8_t *capture_data; +}; + +struct rpi_renderer { + struct weston_renderer base; + + int single_buffer; + +#ifdef ENABLE_EGL + EGLDisplay egl_display; + + PFNEGLBINDWAYLANDDISPLAYWL bind_display; + PFNEGLUNBINDWAYLANDDISPLAYWL unbind_display; + PFNEGLQUERYWAYLANDBUFFERWL query_buffer; +#endif + int has_bind_display; +}; + +static inline struct rpir_surface * +to_rpir_surface(struct weston_surface *surface) +{ + return surface->renderer_state; +} + +static inline struct rpir_output * +to_rpir_output(struct weston_output *output) +{ + return output->renderer_state; +} + +static inline struct rpi_renderer * +to_rpi_renderer(struct weston_compositor *compositor) +{ + return container_of(compositor->renderer, struct rpi_renderer, base); +} + +static inline int +int_max(int a, int b) +{ + return a > b ? a : b; +} + +static inline void +int_swap(int *a, int *b) +{ + int tmp = *a; + *a = *b; + *b = tmp; +} + +static uint8_t +float2uint8(float f) +{ + int v = roundf(f * 255.0f); + + return v < 0 ? 0 : (v > 255 ? 255 : v); +} + +static void +rpi_resource_init(struct rpi_resource *resource) +{ + resource->handle = DISPMANX_NO_HANDLE; +} + +static void +rpi_resource_release(struct rpi_resource *resource) +{ + if (resource->handle == DISPMANX_NO_HANDLE) + return; + + vc_dispmanx_resource_delete(resource->handle); + DBG("resource %p release\n", resource); + resource->handle = DISPMANX_NO_HANDLE; +} + +static int +rpi_resource_realloc(struct rpi_resource *resource, VC_IMAGE_TYPE_T ifmt, + int width, int height, int stride, int buffer_height) +{ + uint32_t dummy; + + if (resource->handle != DISPMANX_NO_HANDLE && + resource->width == width && + resource->height == height && + resource->stride == stride && + resource->buffer_height == buffer_height && + resource->ifmt == ifmt) + return 0; + + rpi_resource_release(resource); + + /* NOTE: if stride is not a multiple of 16 pixels in bytes, + * the vc_image_* functions may break. Dispmanx elements + * should be fine, though. Buffer_height probably has similar + * constraints, too. + */ + resource->handle = + vc_dispmanx_resource_create(ifmt, + width | (stride << 16), + height | (buffer_height << 16), + &dummy); + if (resource->handle == DISPMANX_NO_HANDLE) + return -1; + + resource->width = width; + resource->height = height; + resource->stride = stride; + resource->buffer_height = buffer_height; + resource->ifmt = ifmt; + DBG("resource %p alloc\n", resource); + return 1; +} + +/* A firmware workaround for broken ALPHA_PREMULT + ALPHA_MIX hardware. */ +#define PREMULT_ALPHA_FLAG (1 << 31) + +static VC_IMAGE_TYPE_T +shm_buffer_get_vc_format(struct wl_shm_buffer *buffer) +{ + switch (wl_shm_buffer_get_format(buffer)) { + case WL_SHM_FORMAT_XRGB8888: + return VC_IMAGE_XRGB8888; + case WL_SHM_FORMAT_ARGB8888: + return VC_IMAGE_ARGB8888 | PREMULT_ALPHA_FLAG; + case WL_SHM_FORMAT_RGB565: + return VC_IMAGE_RGB565; + default: + /* invalid format */ + return VC_IMAGE_MIN; + } +} + +static int +rpi_resource_update(struct rpi_resource *resource, struct weston_buffer *buffer, + pixman_region32_t *region) +{ + pixman_region32_t write_region; + pixman_box32_t *r; + VC_RECT_T rect; + VC_IMAGE_TYPE_T ifmt; + uint32_t *pixels; + int width; + int height; + int stride; + int ret; +#ifdef HAVE_RESOURCE_WRITE_DATA_RECT + int n; +#endif + + if (!buffer) + return -1; + + ifmt = shm_buffer_get_vc_format(buffer->shm_buffer); + width = wl_shm_buffer_get_width(buffer->shm_buffer); + height = wl_shm_buffer_get_height(buffer->shm_buffer); + stride = wl_shm_buffer_get_stride(buffer->shm_buffer); + pixels = wl_shm_buffer_get_data(buffer->shm_buffer); + + ret = rpi_resource_realloc(resource, ifmt & ~PREMULT_ALPHA_FLAG, + width, height, stride, height); + if (ret < 0) + return -1; + + pixman_region32_init_rect(&write_region, 0, 0, width, height); + if (ret == 0) + pixman_region32_intersect(&write_region, + &write_region, region); + +#ifdef HAVE_RESOURCE_WRITE_DATA_RECT + /* XXX: Can this do a format conversion, so that scanout does not have to? */ + r = pixman_region32_rectangles(&write_region, &n); + while (n--) { + vc_dispmanx_rect_set(&rect, r[n].x1, r[n].y1, + r[n].x2 - r[n].x1, r[n].y2 - r[n].y1); + + ret = vc_dispmanx_resource_write_data_rect(resource->handle, + ifmt, stride, + pixels, &rect, + rect.x, rect.y); + DBG("%s: %p %ux%u@%u,%u, ret %d\n", __func__, resource, + rect.width, rect.height, rect.x, rect.y, ret); + if (ret) + break; + } +#else + /* vc_dispmanx_resource_write_data() ignores ifmt, + * rect.x, rect.width, and uses stride only for computing + * the size of the transfer as rect.height * stride. + * Therefore we can only write rows starting at x=0. + * To be able to write more than one scanline at a time, + * the resource must have been created with the same stride + * as used here, and we must write full scanlines. + */ + + r = pixman_region32_extents(&write_region); + vc_dispmanx_rect_set(&rect, 0, r->y1, width, r->y2 - r->y1); + ret = vc_dispmanx_resource_write_data(resource->handle, + ifmt, stride, pixels, &rect); + DBG("%s: %p %ux%u@%u,%u, ret %d\n", __func__, resource, + width, r->y2 - r->y1, 0, r->y1, ret); +#endif + + pixman_region32_fini(&write_region); + + return ret ? -1 : 0; +} + +static struct rpir_surface * +rpir_surface_create(struct rpi_renderer *renderer) +{ + struct rpir_surface *surface; + + surface = calloc(1, sizeof *surface); + if (!surface) + return NULL; + + wl_list_init(&surface->link); + surface->single_buffer = renderer->single_buffer; + surface->handle = DISPMANX_NO_HANDLE; + rpi_resource_init(&surface->resources[0]); + rpi_resource_init(&surface->resources[1]); + surface->front = &surface->resources[0]; + if (surface->single_buffer) + surface->back = &surface->resources[0]; + else + surface->back = &surface->resources[1]; + surface->buffer_type = BUFFER_TYPE_NULL; + + pixman_region32_init(&surface->prev_damage); + + return surface; +} + +static void +rpir_surface_destroy(struct rpir_surface *surface) +{ + wl_list_remove(&surface->link); + + if (surface->handle != DISPMANX_NO_HANDLE) + weston_log("ERROR rpi: destroying on-screen element\n"); + + pixman_region32_fini(&surface->prev_damage); + rpi_resource_release(&surface->resources[0]); + rpi_resource_release(&surface->resources[1]); + DBG("rpir_surface %p destroyed (%u)\n", surface, surface->handle); + + if (surface->egl_back != NULL) { + weston_buffer_reference(&surface->egl_back->buffer_ref, NULL); + free(surface->egl_back); + surface->egl_back = NULL; + } + + if (surface->egl_front != NULL) { + weston_buffer_reference(&surface->egl_front->buffer_ref, NULL); + free(surface->egl_front); + surface->egl_front = NULL; + } + + if (surface->egl_old_front != NULL) { + weston_buffer_reference(&surface->egl_old_front->buffer_ref, NULL); + free(surface->egl_old_front); + surface->egl_old_front = NULL; + } + + free(surface); +} + +static int +rpir_surface_damage(struct rpir_surface *surface, struct weston_buffer *buffer, + pixman_region32_t *damage) +{ + pixman_region32_t upload; + int ret; + + if (!pixman_region32_not_empty(damage)) + return 0; + + DBG("rpir_surface %p update resource %p\n", surface, surface->back); + + /* XXX: todo: if no surface->handle, update front buffer directly + * to avoid creating a new back buffer */ + if (surface->single_buffer) { + ret = rpi_resource_update(surface->front, buffer, damage); + } else { + pixman_region32_init(&upload); + pixman_region32_union(&upload, &surface->prev_damage, damage); + ret = rpi_resource_update(surface->back, buffer, &upload); + pixman_region32_fini(&upload); + } + + pixman_region32_copy(&surface->prev_damage, damage); + surface->need_swap = 1; + + return ret; +} + +static void +matrix_type_str(struct weston_matrix *matrix, char *buf, int len) +{ + static const char types[33] = "TSRO"; + unsigned mask = matrix->type; + int i = 0; + + while (mask && i < len - 1) { + if (mask & (1u << i)) + *buf++ = types[i]; + mask &= ~(1u << i); + i++; + } + *buf = '\0'; +} + +static void +log_print_matrix(struct weston_matrix *matrix) +{ + char typestr[6]; + float *d = matrix->d; + + matrix_type_str(matrix, typestr, sizeof typestr); + weston_log_continue("%14.6e %14.6e %14.6e %14.6e\n", + d[0], d[4], d[8], d[12]); + weston_log_continue("%14.6e %14.6e %14.6e %14.6e\n", + d[1], d[5], d[9], d[13]); + weston_log_continue("%14.6e %14.6e %14.6e %14.6e\n", + d[2], d[6], d[10], d[14]); + weston_log_continue("%14.6e %14.6e %14.6e %14.6e type: %s\n", + d[3], d[7], d[11], d[15], typestr); +} + +static void +warn_bad_matrix(struct weston_matrix *total, struct weston_matrix *output, + struct weston_matrix *surface) +{ + static int n_warn; + char typestr[6]; + + if (n_warn++ == 10) + weston_log("%s: not showing more warnings\n", __func__); + + if (n_warn > 10) + return; + + weston_log("%s: warning: total transformation is not renderable:\n", + __func__); + log_print_matrix(total); + + matrix_type_str(surface, typestr, sizeof typestr); + weston_log_continue("surface matrix type: %s\n", typestr); + matrix_type_str(output, typestr, sizeof typestr); + weston_log_continue("output matrix type: %s\n", typestr); +} + +/*#define SURFACE_TRANSFORM */ + +static int +rpir_surface_compute_rects(struct rpir_surface *surface, + VC_RECT_T *src_rect, VC_RECT_T *dst_rect, + VC_IMAGE_TRANSFORM_T *flipmask) +{ + struct weston_output *output_base = surface->surface->output; + struct rpir_output *output = to_rpir_output(output_base); + struct weston_matrix matrix = surface->surface->transform.matrix; + VC_IMAGE_TRANSFORM_T flipt = 0; + int src_x, src_y; + int dst_x, dst_y; + int src_width, src_height; + int dst_width, dst_height; + struct weston_vector p1 = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; + struct weston_vector p2 = {{ 0.0f, 0.0f, 0.0f, 1.0f }}; + int t; + int over; + + /* XXX: take buffer transform into account */ + + /* src is in 16.16, dst is in 32.0 fixed point. + * Negative values are not allowed in VC_RECT_T. + * Clip size to output boundaries, firmware ignores + * huge elements like 8192x8192. + */ + + src_x = 0 << 16; + src_y = 0 << 16; + + if (surface->buffer_type == BUFFER_TYPE_EGL) { + struct weston_buffer *buffer = surface->egl_front->buffer_ref.buffer; + + src_width = buffer->width << 16; + src_height = buffer->height << 16; + } else { + src_width = surface->front->width << 16; + src_height = surface->front->height << 16; + } + + weston_matrix_multiply(&matrix, &output->matrix); + +#ifdef SURFACE_TRANSFORM + if (matrix.type >= WESTON_MATRIX_TRANSFORM_OTHER) { +#else + if (matrix.type >= WESTON_MATRIX_TRANSFORM_ROTATE) { +#endif + warn_bad_matrix(&matrix, &output->matrix, + &surface->surface->transform.matrix); + } else { + if (matrix.type & WESTON_MATRIX_TRANSFORM_ROTATE) { + if (fabsf(matrix.d[0]) < 1e-4f && + fabsf(matrix.d[5]) < 1e-4f) { + flipt |= TRANSFORM_TRANSPOSE; + } else if (fabsf(matrix.d[1]) < 1e-4 && + fabsf(matrix.d[4]) < 1e-4) { + /* no transpose */ + } else { + warn_bad_matrix(&matrix, &output->matrix, + &surface->surface->transform.matrix); + } + } + } + + p2.f[0] = surface->surface->geometry.width; + p2.f[1] = surface->surface->geometry.height; + + /* transform top-left and bot-right corner into screen coordinates */ + weston_matrix_transform(&matrix, &p1); + weston_matrix_transform(&matrix, &p2); + + /* Compute the destination rectangle on screen, converting + * negative dimensions to flips. + */ + + dst_width = round(p2.f[0] - p1.f[0]); + if (dst_width < 0) { + dst_x = round(p2.f[0]); + dst_width = -dst_width; + + if (!(flipt & TRANSFORM_TRANSPOSE)) + flipt |= TRANSFORM_HFLIP; + else + flipt |= TRANSFORM_VFLIP; + } else { + dst_x = round(p1.f[0]); + } + + dst_height = round(p2.f[1] - p1.f[1]); + if (dst_height < 0) { + dst_y = round(p2.f[1]); + dst_height = -dst_height; + + if (!(flipt & TRANSFORM_TRANSPOSE)) + flipt |= TRANSFORM_VFLIP; + else + flipt |= TRANSFORM_HFLIP; + } else { + dst_y = round(p1.f[1]); + } + + if (dst_width == 0 || dst_height == 0) { + DBG("ignored, zero surface area before clipping\n"); + return -1; + } + +#ifdef SURFACE_TRANSFORM + /* Dispmanx works as if you flipped the whole screen, when + * you flip an element. But, we want to flip an element in place. + * XXX: fixme + */ + if (flipt & TRANSFORM_HFLIP) + dst_x = output_base->width - dst_x; + if (flipt & TRANSFORM_VFLIP) + dst_y = output_base->height - dst_y; + if (flipt & TRANSFORM_TRANSPOSE) { + int_swap(&dst_x, &dst_y); + int_swap(&dst_width, &dst_height); + } +#else + switch (output_base->transform) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + flipt = TRANSFORM_HFLIP; + break; + case WL_OUTPUT_TRANSFORM_NORMAL: + flipt = 0; + break; + + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + flipt = TRANSFORM_HFLIP | TRANSFORM_VFLIP | TRANSFORM_TRANSPOSE; + break; + case WL_OUTPUT_TRANSFORM_90: + flipt = TRANSFORM_VFLIP | TRANSFORM_TRANSPOSE; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + flipt = TRANSFORM_VFLIP; + break; + case WL_OUTPUT_TRANSFORM_180: + flipt = TRANSFORM_HFLIP | TRANSFORM_VFLIP; + break; + + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + flipt = TRANSFORM_TRANSPOSE; + break; + case WL_OUTPUT_TRANSFORM_270: + flipt = TRANSFORM_HFLIP | TRANSFORM_TRANSPOSE; + break; + default: + break; + } +#endif + + /* clip destination rectangle to screen dimensions */ + + if (dst_x < 0) { + t = (int64_t)dst_x * src_width / dst_width; + src_width += t; + dst_width += dst_x; + src_x -= t; + dst_x = 0; + } + + if (dst_y < 0) { + t = (int64_t)dst_y * src_height / dst_height; + src_height += t; + dst_height += dst_y; + src_y -= t; + dst_y = 0; + } + + over = dst_x + dst_width - output_base->width; + if (over > 0) { + t = (int64_t)over * src_width / dst_width; + src_width -= t; + dst_width -= over; + } + + over = dst_y + dst_height - output_base->height; + if (over > 0) { + t = (int64_t)over * src_height / dst_height; + src_height -= t; + dst_height -= over; + } + + src_width = int_max(src_width, 0); + src_height = int_max(src_height, 0); + + DBG("rpir_surface %p %dx%d: p1 %f, %f; p2 %f, %f\n", surface, + surface->surface->geometry.width, + surface->surface->geometry.height, + p1.f[0], p1.f[1], p2.f[0], p2.f[1]); + DBG("src rect %d;%d, %d;%d, %d;%dx%d;%d\n", + src_x >> 16, src_x & 0xffff, + src_y >> 16, src_y & 0xffff, + src_width >> 16, src_width & 0xffff, + src_height >> 16, src_height & 0xffff); + DBG("dest rect %d, %d, %dx%d%s%s%s\n", + dst_x, dst_y, dst_width, dst_height, + (flipt & TRANSFORM_HFLIP) ? " hflip" : "", + (flipt & TRANSFORM_VFLIP) ? " vflip" : "", + (flipt & TRANSFORM_TRANSPOSE) ? " transp" : ""); + + assert(src_x >= 0); + assert(src_y >= 0); + assert(dst_x >= 0); + assert(dst_y >= 0); + + if (dst_width < 1 || dst_height < 1) { + DBG("ignored, zero surface area after clipping\n"); + return -1; + } + + /* EGL buffers will be upside-down related to what DispmanX expects */ + if (surface->buffer_type == BUFFER_TYPE_EGL) + flipt ^= TRANSFORM_VFLIP; + + vc_dispmanx_rect_set(src_rect, src_x, src_y, src_width, src_height); + vc_dispmanx_rect_set(dst_rect, dst_x, dst_y, dst_width, dst_height); + *flipmask = flipt; + + return 0; +} + +static DISPMANX_TRANSFORM_T +vc_image2dispmanx_transform(VC_IMAGE_TRANSFORM_T t) +{ + /* XXX: uhh, are these right? */ + switch (t) { + case VC_IMAGE_ROT0: + return DISPMANX_NO_ROTATE; + case VC_IMAGE_MIRROR_ROT0: + return DISPMANX_FLIP_HRIZ; + case VC_IMAGE_MIRROR_ROT180: + return DISPMANX_FLIP_VERT; + case VC_IMAGE_ROT180: + return DISPMANX_ROTATE_180; + case VC_IMAGE_MIRROR_ROT90: + return DISPMANX_ROTATE_90 | DISPMANX_FLIP_HRIZ; + case VC_IMAGE_ROT270: + return DISPMANX_ROTATE_270; + case VC_IMAGE_ROT90: + return DISPMANX_ROTATE_90; + case VC_IMAGE_MIRROR_ROT270: + return DISPMANX_ROTATE_270 | DISPMANX_FLIP_VERT; + default: + assert(0 && "bad VC_IMAGE_TRANSFORM_T"); + return DISPMANX_NO_ROTATE; + } +} + + +static DISPMANX_RESOURCE_HANDLE_T +rpir_surface_get_resource(struct rpir_surface *surface) +{ + switch (surface->buffer_type) { + case BUFFER_TYPE_SHM: + case BUFFER_TYPE_NULL: + return surface->front->handle; + case BUFFER_TYPE_EGL: + if (surface->egl_front != NULL) + return surface->egl_front->resource_handle; + default: + return DISPMANX_NO_HANDLE; + } +} + +static int +rpir_surface_dmx_add(struct rpir_surface *surface, struct rpir_output *output, + DISPMANX_UPDATE_HANDLE_T update, int layer) +{ + /* Do not use DISPMANX_FLAGS_ALPHA_PREMULT here. + * If you define PREMULT and ALPHA_MIX, the hardware will not + * multiply the source color with the element alpha, leading to + * bad colors. Instead, we define PREMULT during pixel data upload. + */ + VC_DISPMANX_ALPHA_T alphasetup = { + DISPMANX_FLAGS_ALPHA_FROM_SOURCE | + DISPMANX_FLAGS_ALPHA_MIX, + float2uint8(surface->surface->alpha), /* opacity 0-255 */ + 0 /* mask resource handle */ + }; + VC_RECT_T dst_rect; + VC_RECT_T src_rect; + VC_IMAGE_TRANSFORM_T flipmask; + int ret; + DISPMANX_RESOURCE_HANDLE_T resource_handle; + + resource_handle = rpir_surface_get_resource(surface); + if (resource_handle == DISPMANX_NO_HANDLE) { + weston_log("%s: no buffer yet, aborting\n", __func__); + return 0; + } + + ret = rpir_surface_compute_rects(surface, &src_rect, &dst_rect, + &flipmask); + if (ret < 0) + return 0; + + surface->handle = vc_dispmanx_element_add( + update, + output->display, + layer, + &dst_rect, + resource_handle, + &src_rect, + DISPMANX_PROTECTION_NONE, + &alphasetup, + NULL /* clamp */, + vc_image2dispmanx_transform(flipmask)); + DBG("rpir_surface %p add %u, alpha %f resource %d\n", surface, + surface->handle, surface->surface->alpha, resource_handle); + + return 1; +} + +static void +rpir_surface_dmx_swap(struct rpir_surface *surface, + DISPMANX_UPDATE_HANDLE_T update) +{ + VC_RECT_T rect; + pixman_box32_t *r; + + /* XXX: skip, iff resource was not reallocated, and single-buffering */ + vc_dispmanx_element_change_source(update, surface->handle, + surface->front->handle); + + /* This is current damage now, after rpir_surface_damage() */ + r = pixman_region32_extents(&surface->prev_damage); + + vc_dispmanx_rect_set(&rect, r->x1, r->y1, + r->x2 - r->x1, r->y2 - r->y1); + vc_dispmanx_element_modified(update, surface->handle, &rect); + DBG("rpir_surface %p swap\n", surface); +} + +static int +rpir_surface_dmx_move(struct rpir_surface *surface, + DISPMANX_UPDATE_HANDLE_T update, int layer) +{ + uint8_t alpha = float2uint8(surface->surface->alpha); + VC_RECT_T dst_rect; + VC_RECT_T src_rect; + VC_IMAGE_TRANSFORM_T flipmask; + int ret; + + /* XXX: return early, if all attributes stay the same */ + + if (surface->buffer_type == BUFFER_TYPE_EGL) { + DISPMANX_RESOURCE_HANDLE_T resource_handle; + + resource_handle = rpir_surface_get_resource(surface); + if (resource_handle == DISPMANX_NO_HANDLE) { + weston_log("%s: no buffer yet, aborting\n", __func__); + return 0; + } + + vc_dispmanx_element_change_source(update, + surface->handle, + resource_handle); + } + + ret = rpir_surface_compute_rects(surface, &src_rect, &dst_rect, + &flipmask); + if (ret < 0) + return 0; + + ret = vc_dispmanx_element_change_attributes( + update, + surface->handle, + ELEMENT_CHANGE_LAYER | + ELEMENT_CHANGE_OPACITY | + ELEMENT_CHANGE_TRANSFORM | + ELEMENT_CHANGE_DEST_RECT | + ELEMENT_CHANGE_SRC_RECT, + layer, + alpha, + &dst_rect, + &src_rect, + DISPMANX_NO_HANDLE, + /* This really is DISPMANX_TRANSFORM_T, no matter + * what the header says. */ + vc_image2dispmanx_transform(flipmask)); + DBG("rpir_surface %p move\n", surface); + + if (ret) + return -1; + + return 1; +} + +static void +rpir_surface_dmx_remove(struct rpir_surface *surface, + DISPMANX_UPDATE_HANDLE_T update) +{ + if (surface->handle == DISPMANX_NO_HANDLE) + return; + + vc_dispmanx_element_remove(update, surface->handle); + DBG("rpir_surface %p remove %u\n", surface, surface->handle); + surface->handle = DISPMANX_NO_HANDLE; +} + +static void +rpir_surface_swap_pointers(struct rpir_surface *surface) +{ + struct rpi_resource *tmp; + + tmp = surface->front; + surface->front = surface->back; + surface->back = tmp; + surface->need_swap = 0; + DBG("new back %p, new front %p\n", surface->back, surface->front); +} + +static int +is_surface_not_visible(struct weston_surface *surface) +{ + /* Return true, if surface is guaranteed to be totally obscured. */ + int ret; + pixman_region32_t unocc; + + pixman_region32_init(&unocc); + pixman_region32_subtract(&unocc, &surface->transform.boundingbox, + &surface->clip); + ret = !pixman_region32_not_empty(&unocc); + pixman_region32_fini(&unocc); + + return ret; +} + +static void +rpir_surface_update(struct rpir_surface *surface, struct rpir_output *output, + DISPMANX_UPDATE_HANDLE_T update, int layer) +{ + int need_swap = surface->need_swap; + int ret; + int obscured; + + if (surface->buffer_type == BUFFER_TYPE_EGL) { + if (surface->egl_back != NULL) { + assert(surface->egl_old_front == NULL); + surface->egl_old_front = surface->egl_front; + surface->egl_front = surface->egl_back; + surface->egl_back = NULL; + } + if (surface->egl_front->buffer_ref.buffer == NULL) { + weston_log("warning: client unreffed current front buffer\n"); + + wl_list_remove(&surface->link); + if (surface->handle == DISPMANX_NO_HANDLE) { + wl_list_init(&surface->link); + } else { + rpir_surface_dmx_remove(surface, update); + wl_list_insert(&output->surface_cleanup_list, + &surface->link); + } + + goto out; + } + } else { + if (need_swap) + rpir_surface_swap_pointers(surface); + } + + obscured = is_surface_not_visible(surface->surface); + if (obscured) { + DBG("rpir_surface %p totally obscured.\n", surface); + + wl_list_remove(&surface->link); + if (surface->handle == DISPMANX_NO_HANDLE) { + wl_list_init(&surface->link); + } else { + rpir_surface_dmx_remove(surface, update); + wl_list_insert(&output->surface_cleanup_list, + &surface->link); + } + + goto out; + } + + if (surface->handle == DISPMANX_NO_HANDLE) { + ret = rpir_surface_dmx_add(surface, output, update, layer); + if (ret == 0) { + wl_list_remove(&surface->link); + wl_list_init(&surface->link); + } else if (ret < 0) { + weston_log("ERROR rpir_surface_dmx_add() failed.\n"); + } + } else { + if (need_swap) + rpir_surface_dmx_swap(surface, update); + + ret = rpir_surface_dmx_move(surface, update, layer); + if (ret == 0) { + rpir_surface_dmx_remove(surface, update); + + wl_list_remove(&surface->link); + wl_list_insert(&output->surface_cleanup_list, + &surface->link); + } else if (ret < 0) { + weston_log("ERROR rpir_surface_dmx_move() failed.\n"); + } + } + +out: + surface->layer = layer; +} + +static int +rpi_renderer_read_pixels(struct weston_output *base, + pixman_format_code_t format, void *pixels, + uint32_t x, uint32_t y, + uint32_t width, uint32_t height) +{ + struct rpir_output *output = to_rpir_output(base); + struct rpi_resource *buffer = &output->capture_buffer; + VC_RECT_T rect; + uint32_t fb_width, fb_height; + uint32_t dst_pitch; + uint32_t i; + int ret; + + fb_width = base->current_mode->width; + fb_height = base->current_mode->height; + + DBG("%s(%u, %u, %u, %u), resource %p\n", __func__, + x, y, width, height, buffer); + + if (format != PIXMAN_a8r8g8b8) { + weston_log("rpi-renderer error: bad read_format\n"); + return -1; + } + + dst_pitch = fb_width * 4; + + if (buffer->handle == DISPMANX_NO_HANDLE) { + free(output->capture_data); + output->capture_data = NULL; + + ret = rpi_resource_realloc(buffer, VC_IMAGE_ARGB8888, + fb_width, fb_height, + dst_pitch, fb_height); + if (ret < 0) { + weston_log("rpi-renderer error: " + "allocating read buffer failed\n"); + return -1; + } + + ret = vc_dispmanx_snapshot(output->display, buffer->handle, + VC_IMAGE_ROT0); + if (ret) { + weston_log("rpi-renderer error: " + "vc_dispmanx_snapshot returned %d\n", ret); + return -1; + } + DBG("%s: snapshot done.\n", __func__); + } + + /* + * If vc_dispmanx_resource_read_data was able to read sub-rectangles, + * we could read directly into 'pixels'. But it cannot, it does not + * use rect.x or rect.width, and does this: + * host_start = (uint8_t *)dst_address + (dst_pitch * p_rect->y); + * In other words, it is only good for reading the full buffer in + * one go. + */ + vc_dispmanx_rect_set(&rect, 0, 0, fb_width, fb_height); + + if (x == 0 && y == 0 && width == fb_width && height == fb_height) { + ret = vc_dispmanx_resource_read_data(buffer->handle, &rect, + pixels, dst_pitch); + if (ret) { + weston_log("rpi-renderer error: " + "resource_read_data returned %d\n", ret); + return -1; + } + DBG("%s: full frame done.\n", __func__); + return 0; + } + + if (!output->capture_data) { + output->capture_data = malloc(fb_height * dst_pitch); + if (!output->capture_data) { + weston_log("rpi-renderer error: " + "out of memory\n"); + return -1; + } + + ret = vc_dispmanx_resource_read_data(buffer->handle, &rect, + output->capture_data, + dst_pitch); + if (ret) { + weston_log("rpi-renderer error: " + "resource_read_data returned %d\n", ret); + return -1; + } + } + + for (i = 0; i < height; i++) { + uint8_t *src = output->capture_data + + (y + i) * dst_pitch + x * 4; + uint8_t *dst = (uint8_t *)pixels + i * width * 4; + memcpy(dst, src, width * 4); + } + + return 0; +} + +static void +rpir_output_dmx_remove_all(struct rpir_output *output, + DISPMANX_UPDATE_HANDLE_T update) +{ + struct rpir_surface *surface; + + while (!wl_list_empty(&output->surface_list)) { + surface = container_of(output->surface_list.next, + struct rpir_surface, link); + rpir_surface_dmx_remove(surface, update); + + wl_list_remove(&surface->link); + wl_list_insert(&output->surface_cleanup_list, &surface->link); + } +} + +static void +output_compute_matrix(struct weston_output *base) +{ + struct rpir_output *output = to_rpir_output(base); + struct weston_matrix *matrix = &output->matrix; + const float half_w = 0.5f * base->width; + const float half_h = 0.5f * base->height; + float mag; + float dx, dy; + + weston_matrix_init(matrix); + weston_matrix_translate(matrix, -base->x, -base->y, 0.0f); + +#ifdef SURFACE_TRANSFORM + weston_matrix_translate(matrix, -half_w, -half_h, 0.0f); + switch (base->transform) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + weston_matrix_scale(matrix, -1.0f, 1.0f, 1.0f); + case WL_OUTPUT_TRANSFORM_NORMAL: + /* weston_matrix_rotate_xy(matrix, 1.0f, 0.0f); no-op */ + weston_matrix_translate(matrix, half_w, half_h, 0.0f); + break; + + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + weston_matrix_scale(matrix, -1.0f, 1.0f, 1.0f); + case WL_OUTPUT_TRANSFORM_90: + weston_matrix_rotate_xy(matrix, 0.0f, 1.0f); + weston_matrix_translate(matrix, half_h, half_w, 0.0f); + break; + + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + weston_matrix_scale(matrix, -1.0f, 1.0f, 1.0f); + case WL_OUTPUT_TRANSFORM_180: + weston_matrix_rotate_xy(matrix, -1.0f, 0.0f); + weston_matrix_translate(matrix, half_w, half_h, 0.0f); + break; + + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + weston_matrix_scale(matrix, -1.0f, 1.0f, 1.0f); + case WL_OUTPUT_TRANSFORM_270: + weston_matrix_rotate_xy(matrix, 0.0f, -1.0f); + weston_matrix_translate(matrix, half_h, half_w, 0.0f); + break; + + default: + break; + } +#endif + + if (base->zoom.active) { + /* The base->zoom stuff is in GL coordinate system */ + mag = 1.0f / (1.0f - base->zoom.spring_z.current); + dx = -(base->zoom.trans_x + 1.0f) * half_w; + dy = -(base->zoom.trans_y + 1.0f) * half_h; + weston_matrix_translate(matrix, dx, dy, 0.0f); + weston_matrix_scale(matrix, mag, mag, 1.0f); + weston_matrix_translate(matrix, half_w, half_h, 0.0f); + } +} + +/* Note: this won't work right for multiple outputs. A DispmanX Element + * is tied to one DispmanX Display, i.e. output. + */ +static void +rpi_renderer_repaint_output(struct weston_output *base, + pixman_region32_t *output_damage) +{ + struct weston_compositor *compositor = base->compositor; + struct rpir_output *output = to_rpir_output(base); + struct weston_surface *ws; + struct rpir_surface *surface; + struct wl_list done_list; + int layer = 1; + + assert(output->update != DISPMANX_NO_HANDLE); + + output_compute_matrix(base); + + rpi_resource_release(&output->capture_buffer); + free(output->capture_data); + output->capture_data = NULL; + + /* update all renderable surfaces */ + wl_list_init(&done_list); + wl_list_for_each_reverse(ws, &compositor->surface_list, link) { + if (ws->plane != &compositor->primary_plane) + continue; + + surface = to_rpir_surface(ws); + assert(!wl_list_empty(&surface->link) || + surface->handle == DISPMANX_NO_HANDLE); + + wl_list_remove(&surface->link); + wl_list_insert(&done_list, &surface->link); + rpir_surface_update(surface, output, output->update, layer++); + } + + /* Remove all surfaces that are still on screen, but were + * not rendered this time. + */ + rpir_output_dmx_remove_all(output, output->update); + + wl_list_insert_list(&output->surface_list, &done_list); + output->update = DISPMANX_NO_HANDLE; + + /* The frame_signal is emitted in rpi_renderer_finish_frame(), + * so that the firmware can capture the up-to-date contents. + */ +} + +static void +rpi_renderer_flush_damage(struct weston_surface *base) +{ + /* Called for every surface just before repainting it, if + * having an shm buffer. + */ + struct rpir_surface *surface = to_rpir_surface(base); + struct weston_buffer *buffer = surface->buffer_ref.buffer; + int ret; + + assert(buffer); + assert(wl_shm_buffer_get(buffer->resource)); + + ret = rpir_surface_damage(surface, buffer, &base->damage); + if (ret) + weston_log("%s error: updating Dispmanx resource failed.\n", + __func__); + + weston_buffer_reference(&surface->buffer_ref, NULL); +} + +static void +rpi_renderer_attach(struct weston_surface *base, struct weston_buffer *buffer) +{ + /* Called every time a client commits an attach. */ + struct rpir_surface *surface = to_rpir_surface(base); + + assert(surface); + if (!surface) + return; + + if (surface->buffer_type == BUFFER_TYPE_SHM) { + if (!surface->single_buffer) + /* XXX: need to check if in middle of update */ + rpi_resource_release(surface->back); + + if (surface->handle == DISPMANX_NO_HANDLE) + /* XXX: cannot do this, if middle of an update */ + rpi_resource_release(surface->front); + + weston_buffer_reference(&surface->buffer_ref, NULL); + } + + /* If buffer is NULL, Weston core unmaps the surface, the surface + * will not appear in repaint list, and so rpi_renderer_repaint_output + * will remove the DispmanX element. Later, for SHM, also the front + * buffer will be released in the cleanup_list processing. + */ + if (!buffer) + return; + + if (wl_shm_buffer_get(buffer->resource)) { + surface->buffer_type = BUFFER_TYPE_SHM; + buffer->shm_buffer = wl_shm_buffer_get(buffer->resource); + buffer->width = wl_shm_buffer_get_width(buffer->shm_buffer); + buffer->height = wl_shm_buffer_get_height(buffer->shm_buffer); + + weston_buffer_reference(&surface->buffer_ref, buffer); + } else { +#if ENABLE_EGL + struct rpi_renderer *renderer = to_rpi_renderer(base->compositor); + struct wl_resource *wl_resource = buffer->resource; + + if (!renderer->has_bind_display || + !renderer->query_buffer(renderer->egl_display, + wl_resource, + EGL_WIDTH, &buffer->width)) { + weston_log("unhandled buffer type!\n"); + weston_buffer_reference(&surface->buffer_ref, NULL); + surface->buffer_type = BUFFER_TYPE_NULL; + } + + renderer->query_buffer(renderer->egl_display, + wl_resource, + EGL_HEIGHT, &buffer->height); + + surface->buffer_type = BUFFER_TYPE_EGL; + + if(surface->egl_back == NULL) + surface->egl_back = calloc(1, sizeof *surface->egl_back); + + weston_buffer_reference(&surface->egl_back->buffer_ref, buffer); + surface->egl_back->resource_handle = + vc_dispmanx_get_handle_from_wl_buffer(wl_resource); +#else + weston_log("unhandled buffer type!\n"); + weston_buffer_reference(&surface->buffer_ref, NULL); + surface->buffer_type = BUFFER_TYPE_NULL; +#endif + } +} + +static int +rpi_renderer_create_surface(struct weston_surface *base) +{ + struct rpi_renderer *renderer = to_rpi_renderer(base->compositor); + struct rpir_surface *surface; + + assert(base->renderer_state == NULL); + + surface = rpir_surface_create(renderer); + if (!surface) + return -1; + + surface->surface = base; + base->renderer_state = surface; + return 0; +} + +static void +rpi_renderer_surface_set_color(struct weston_surface *base, + float red, float green, float blue, float alpha) +{ + struct rpir_surface *surface = to_rpir_surface(base); + uint8_t color[4]; + VC_RECT_T rect; + int ret; + + assert(surface); + + ret = rpi_resource_realloc(surface->back, VC_IMAGE_ARGB8888, + 1, 1, 4, 1); + if (ret < 0) { + weston_log("Error: %s: rpi_resource_realloc failed.\n", + __func__); + return; + } + + color[0] = float2uint8(blue); + color[1] = float2uint8(green); + color[2] = float2uint8(red); + color[3] = float2uint8(alpha); + + vc_dispmanx_rect_set(&rect, 0, 0, 1, 1); + ret = vc_dispmanx_resource_write_data(surface->back->handle, + VC_IMAGE_ARGB8888, + 4, color, &rect); + if (ret) { + weston_log("Error: %s: resource_write_data failed.\n", + __func__); + return; + } + + DBG("%s: resource %p solid color BGRA %u,%u,%u,%u\n", __func__, + surface->back, color[0], color[1], color[2], color[3]); + + /*pixman_region32_copy(&surface->prev_damage, damage);*/ + surface->need_swap = 1; +} + +static void +rpi_renderer_destroy_surface(struct weston_surface *base) +{ + struct rpir_surface *surface = to_rpir_surface(base); + + assert(surface); + assert(surface->surface == base); + if (!surface) + return; + + surface->surface = NULL; + base->renderer_state = NULL; + + /* If guaranteed to not be on screen, just detroy it. */ + if (wl_list_empty(&surface->link)) + rpir_surface_destroy(surface); + + /* Otherwise, the surface is either on screen and needs + * to be removed by a repaint update, or it is in the + * surface_cleanup_list, and will be destroyed by + * rpi_renderer_finish_frame(). + */ +} + +static void +rpi_renderer_destroy(struct weston_compositor *compositor) +{ + struct rpi_renderer *renderer = to_rpi_renderer(compositor); + +#if ENABLE_EGL + if (renderer->has_bind_display) + renderer->unbind_display(renderer->egl_display, + compositor->wl_display); +#endif + + free(renderer); + compositor->renderer = NULL; +} + +WL_EXPORT int +rpi_renderer_create(struct weston_compositor *compositor, + const struct rpi_renderer_parameters *params) +{ + struct rpi_renderer *renderer; +#if ENABLE_EGL + const char *extensions; + EGLBoolean ret; + EGLint major, minor; +#endif + + weston_log("Initializing the DispmanX compositing renderer\n"); + + renderer = calloc(1, sizeof *renderer); + if (renderer == NULL) + return -1; + + renderer->single_buffer = params->single_buffer; + + renderer->base.read_pixels = rpi_renderer_read_pixels; + renderer->base.repaint_output = rpi_renderer_repaint_output; + renderer->base.flush_damage = rpi_renderer_flush_damage; + renderer->base.attach = rpi_renderer_attach; + renderer->base.create_surface = rpi_renderer_create_surface; + renderer->base.surface_set_color = rpi_renderer_surface_set_color; + renderer->base.destroy_surface = rpi_renderer_destroy_surface; + renderer->base.destroy = rpi_renderer_destroy; + +#ifdef ENABLE_EGL + renderer->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (renderer->egl_display == EGL_NO_DISPLAY) { + weston_log("failed to create EGL display\n"); + return -1; + } + + if (!eglInitialize(renderer->egl_display, &major, &minor)) { + weston_log("failed to initialize EGL display\n"); + return -1; + } + + renderer->bind_display = + (void *) eglGetProcAddress("eglBindWaylandDisplayWL"); + renderer->unbind_display = + (void *) eglGetProcAddress("eglUnbindWaylandDisplayWL"); + renderer->query_buffer = + (void *) eglGetProcAddress("eglQueryWaylandBufferWL"); + + extensions = (const char *) eglQueryString(renderer->egl_display, + EGL_EXTENSIONS); + if (!extensions) { + weston_log("Retrieving EGL extension string failed.\n"); + return -1; + } + + if (strstr(extensions, "EGL_WL_bind_wayland_display")) + renderer->has_bind_display = 1; + + if (renderer->has_bind_display) { + ret = renderer->bind_display(renderer->egl_display, + compositor->wl_display); + if (!ret) + renderer->has_bind_display = 0; + } +#endif + + compositor->renderer = &renderer->base; + compositor->read_format = PIXMAN_a8r8g8b8; + /* WESTON_CAP_ROTATION_ANY not supported */ + + wl_display_add_shm_format(compositor->wl_display, WL_SHM_FORMAT_RGB565); + + return 0; +} + +WL_EXPORT int +rpi_renderer_output_create(struct weston_output *base, + DISPMANX_DISPLAY_HANDLE_T display) +{ + struct rpir_output *output; + + assert(base->renderer_state == NULL); + + output = calloc(1, sizeof *output); + if (!output) + return -1; + + output->display = display; + output->update = DISPMANX_NO_HANDLE; + wl_list_init(&output->surface_list); + wl_list_init(&output->surface_cleanup_list); + rpi_resource_init(&output->capture_buffer); + base->renderer_state = output; + + return 0; +} + +WL_EXPORT void +rpi_renderer_output_destroy(struct weston_output *base) +{ + struct rpir_output *output = to_rpir_output(base); + struct rpir_surface *surface; + DISPMANX_UPDATE_HANDLE_T update; + + rpi_resource_release(&output->capture_buffer); + free(output->capture_data); + output->capture_data = NULL; + + update = vc_dispmanx_update_start(0); + rpir_output_dmx_remove_all(output, update); + vc_dispmanx_update_submit_sync(update); + + while (!wl_list_empty(&output->surface_cleanup_list)) { + surface = container_of(output->surface_cleanup_list.next, + struct rpir_surface, link); + if (surface->surface) + surface->surface->renderer_state = NULL; + rpir_surface_destroy(surface); + } + + free(output); + base->renderer_state = NULL; +} + +WL_EXPORT void +rpi_renderer_set_update_handle(struct weston_output *base, + DISPMANX_UPDATE_HANDLE_T handle) +{ + struct rpir_output *output = to_rpir_output(base); + + output->update = handle; +} + +WL_EXPORT void +rpi_renderer_finish_frame(struct weston_output *base) +{ + struct rpir_output *output = to_rpir_output(base); + struct weston_compositor *compositor = base->compositor; + struct weston_surface *ws; + struct rpir_surface *surface; + + while (!wl_list_empty(&output->surface_cleanup_list)) { + surface = container_of(output->surface_cleanup_list.next, + struct rpir_surface, link); + + if (surface->surface) { + /* The weston_surface still exists, but is + * temporarily not visible, and hence its Element + * was removed. The current front buffer contents + * must be preserved. + */ + if (!surface->single_buffer) + rpi_resource_release(surface->back); + + wl_list_remove(&surface->link); + wl_list_init(&surface->link); + } else { + rpir_surface_destroy(surface); + } + } + + wl_list_for_each(ws, &compositor->surface_list, link) { + surface = to_rpir_surface(ws); + + if (surface->buffer_type != BUFFER_TYPE_EGL) + continue; + + if(surface->egl_old_front == NULL) + continue; + + weston_buffer_reference(&surface->egl_old_front->buffer_ref, NULL); + free(surface->egl_old_front); + surface->egl_old_front = NULL; + } + + wl_signal_emit(&base->frame_signal, base); +} diff --git a/src/rpi-renderer.h b/src/rpi-renderer.h new file mode 100644 index 00000000..28ae3039 --- /dev/null +++ b/src/rpi-renderer.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2013 Raspberry Pi Foundation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef RPI_RENDERER_H +#define RPI_RENDERER_H + +struct rpi_renderer_parameters { + int single_buffer; +}; + +int +rpi_renderer_create(struct weston_compositor *compositor, + const struct rpi_renderer_parameters *params); + +int +rpi_renderer_output_create(struct weston_output *base, + DISPMANX_DISPLAY_HANDLE_T display); + +void +rpi_renderer_output_destroy(struct weston_output *base); + +void +rpi_renderer_set_update_handle(struct weston_output *base, + DISPMANX_UPDATE_HANDLE_T handle); + +void +rpi_renderer_finish_frame(struct weston_output *base); + +#endif /* RPI_RENDERER_H */ diff --git a/src/screenshooter.c b/src/screenshooter.c new file mode 100644 index 00000000..645114d0 --- /dev/null +++ b/src/screenshooter.c @@ -0,0 +1,591 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "compositor.h" +#include "screenshooter-server-protocol.h" + +#include "../wcap/wcap-decode.h" + +struct screenshooter { + struct weston_compositor *ec; + struct wl_global *global; + struct wl_client *client; + struct weston_process process; + struct wl_listener destroy_listener; +}; + +struct screenshooter_frame_listener { + struct wl_listener listener; + struct weston_buffer *buffer; + struct wl_resource *resource; +}; + +static void +copy_bgra_yflip(uint8_t *dst, uint8_t *src, int height, int stride) +{ + uint8_t *end; + + end = dst + height * stride; + while (dst < end) { + memcpy(dst, src, stride); + dst += stride; + src -= stride; + } +} + +static void +copy_bgra(uint8_t *dst, uint8_t *src, int height, int stride) +{ + /* TODO: optimize this out */ + memcpy(dst, src, height * stride); +} + +static void +copy_row_swap_RB(void *vdst, void *vsrc, int bytes) +{ + uint32_t *dst = vdst; + uint32_t *src = vsrc; + uint32_t *end = dst + bytes / 4; + + while (dst < end) { + uint32_t v = *src++; + /* A R G B */ + uint32_t tmp = v & 0xff00ff00; + tmp |= (v >> 16) & 0x000000ff; + tmp |= (v << 16) & 0x00ff0000; + *dst++ = tmp; + } +} + +static void +copy_rgba_yflip(uint8_t *dst, uint8_t *src, int height, int stride) +{ + uint8_t *end; + + end = dst + height * stride; + while (dst < end) { + copy_row_swap_RB(dst, src, stride); + dst += stride; + src -= stride; + } +} + +static void +copy_rgba(uint8_t *dst, uint8_t *src, int height, int stride) +{ + uint8_t *end; + + end = dst + height * stride; + while (dst < end) { + copy_row_swap_RB(dst, src, stride); + dst += stride; + src += stride; + } +} + +static void +screenshooter_frame_notify(struct wl_listener *listener, void *data) +{ + struct screenshooter_frame_listener *l = + container_of(listener, + struct screenshooter_frame_listener, listener); + struct weston_output *output = data; + struct weston_compositor *compositor = output->compositor; + int32_t stride; + uint8_t *pixels, *d, *s; + + output->disable_planes--; + wl_list_remove(&listener->link); + stride = l->buffer->width * (PIXMAN_FORMAT_BPP(compositor->read_format) / 8); + pixels = malloc(stride * l->buffer->height); + + if (pixels == NULL) { + wl_resource_post_no_memory(l->resource); + free(l); + return; + } + + compositor->renderer->read_pixels(output, + compositor->read_format, pixels, + 0, 0, output->current_mode->width, + output->current_mode->height); + + stride = wl_shm_buffer_get_stride(l->buffer->shm_buffer); + + d = wl_shm_buffer_get_data(l->buffer->shm_buffer); + s = pixels + stride * (l->buffer->height - 1); + + switch (compositor->read_format) { + case PIXMAN_a8r8g8b8: + case PIXMAN_x8r8g8b8: + if (compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP) + copy_bgra_yflip(d, s, output->current_mode->height, stride); + else + copy_bgra(d, pixels, output->current_mode->height, stride); + break; + case PIXMAN_x8b8g8r8: + case PIXMAN_a8b8g8r8: + if (compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP) + copy_rgba_yflip(d, s, output->current_mode->height, stride); + else + copy_rgba(d, pixels, output->current_mode->height, stride); + break; + default: + break; + } + + screenshooter_send_done(l->resource); + free(pixels); + free(l); +} + +static void +screenshooter_shoot(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output_resource, + struct wl_resource *buffer_resource) +{ + struct weston_output *output = + wl_resource_get_user_data(output_resource); + struct screenshooter_frame_listener *l; + struct weston_buffer *buffer = + weston_buffer_from_resource(buffer_resource); + + if (buffer == NULL) { + wl_resource_post_no_memory(resource); + return; + } + if (!wl_shm_buffer_get(buffer->resource)) + return; + + buffer->shm_buffer = wl_shm_buffer_get(buffer->resource); + buffer->width = wl_shm_buffer_get_width(buffer->shm_buffer); + buffer->height = wl_shm_buffer_get_height(buffer->shm_buffer); + + if (buffer->width < output->current_mode->width || + buffer->height < output->current_mode->height) + return; + + l = malloc(sizeof *l); + if (l == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + l->buffer = buffer; + l->resource = resource; + + l->listener.notify = screenshooter_frame_notify; + wl_signal_add(&output->frame_signal, &l->listener); + output->disable_planes++; + weston_output_schedule_repaint(output); +} + +struct screenshooter_interface screenshooter_implementation = { + screenshooter_shoot +}; + +static void +bind_shooter(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct screenshooter *shooter = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, + &screenshooter_interface, 1, id); + + if (client != shooter->client) { + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "screenshooter failed: permission denied"); + wl_resource_destroy(resource); + } + + wl_resource_set_implementation(resource, &screenshooter_implementation, + data, NULL); +} + +static void +screenshooter_sigchld(struct weston_process *process, int status) +{ + struct screenshooter *shooter = + container_of(process, struct screenshooter, process); + + shooter->client = NULL; +} + +static void +screenshooter_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + struct screenshooter *shooter = data; + const char *screenshooter_exe = LIBEXECDIR "/weston-screenshooter"; + + if (!shooter->client) + shooter->client = weston_client_launch(shooter->ec, + &shooter->process, + screenshooter_exe, screenshooter_sigchld); +} + +struct weston_recorder { + struct weston_output *output; + uint32_t *frame, *rect; + uint32_t *tmpbuf; + uint32_t total; + int fd; + struct wl_listener frame_listener; + int count; +}; + +static uint32_t * +output_run(uint32_t *p, uint32_t delta, int run) +{ + int i; + + while (run > 0) { + if (run <= 0xe0) { + *p++ = delta | ((run - 1) << 24); + break; + } + + i = 24 - __builtin_clz(run); + *p++ = delta | ((i + 0xe0) << 24); + run -= 1 << (7 + i); + } + + return p; +} + +static uint32_t +component_delta(uint32_t next, uint32_t prev) +{ + unsigned char dr, dg, db; + + dr = (next >> 16) - (prev >> 16); + dg = (next >> 8) - (prev >> 8); + db = (next >> 0) - (prev >> 0); + + return (dr << 16) | (dg << 8) | (db << 0); +} + +static void +transform_rect(struct weston_output *output, pixman_box32_t *r) +{ + pixman_box32_t s = *r; + + switch (output->transform) { + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + s.x1 = output->width - r->x2; + s.x2 = output->width - r->x1; + break; + default: + break; + } + + switch (output->transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_FLIPPED: + r->x1 = s.x1; + r->x2 = s.x2; + break; + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + r->x1 = output->current_mode->width - s.y2; + r->y1 = s.x1; + r->x2 = output->current_mode->width - s.y1; + r->y2 = s.x2; + break; + case WL_OUTPUT_TRANSFORM_180: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + r->x1 = output->current_mode->width - s.x2; + r->y1 = output->current_mode->height - s.y2; + r->x2 = output->current_mode->width - s.x1; + r->y2 = output->current_mode->height - s.y1; + break; + case WL_OUTPUT_TRANSFORM_270: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + r->x1 = s.y1; + r->y1 = output->current_mode->height - s.x2; + r->x2 = s.y2; + r->y2 = output->current_mode->height - s.x1; + break; + default: + break; + } + + r->x1 *= output->current_scale; + r->y1 *= output->current_scale; + r->x2 *= output->current_scale; + r->y2 *= output->current_scale; +} + +static void +weston_recorder_frame_notify(struct wl_listener *listener, void *data) +{ + struct weston_recorder *recorder = + container_of(listener, struct weston_recorder, frame_listener); + struct weston_output *output = data; + struct weston_compositor *compositor = output->compositor; + uint32_t msecs = output->frame_time; + pixman_box32_t *r; + pixman_region32_t damage; + int i, j, k, n, width, height, run, stride; + uint32_t delta, prev, *d, *s, *p, next; + struct { + uint32_t msecs; + uint32_t nrects; + } header; + struct iovec v[2]; + int do_yflip; + int y_orig; + uint32_t *outbuf; + + do_yflip = !!(compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP); + if (do_yflip) + outbuf = recorder->rect; + else + outbuf = recorder->tmpbuf; + + pixman_region32_init(&damage); + pixman_region32_intersect(&damage, &output->region, + &output->previous_damage); + + r = pixman_region32_rectangles(&damage, &n); + if (n == 0) + return; + + for (i = 0; i < n; i++) + transform_rect(output, &r[i]); + + header.msecs = msecs; + header.nrects = n; + v[0].iov_base = &header; + v[0].iov_len = sizeof header; + v[1].iov_base = r; + v[1].iov_len = n * sizeof *r; + recorder->total += writev(recorder->fd, v, 2); + stride = output->current_mode->width; + + for (i = 0; i < n; i++) { + width = r[i].x2 - r[i].x1; + height = r[i].y2 - r[i].y1; + + if (do_yflip) + y_orig = output->current_mode->height - r[i].y2; + else + y_orig = r[i].y1; + + compositor->renderer->read_pixels(output, + compositor->read_format, recorder->rect, + r[i].x1, y_orig, width, height); + + s = recorder->rect; + p = outbuf; + run = prev = 0; /* quiet gcc */ + for (j = 0; j < height; j++) { + if (do_yflip) + y_orig = r[i].y2 - j - 1; + else + y_orig = r[i].y1 + j; + d = recorder->frame + stride * y_orig + r[i].x1; + + for (k = 0; k < width; k++) { + next = *s++; + delta = component_delta(next, *d); + *d++ = next; + if (run == 0 || delta == prev) { + run++; + } else { + p = output_run(p, prev, run); + run = 1; + } + prev = delta; + } + } + + p = output_run(p, prev, run); + + recorder->total += write(recorder->fd, + outbuf, (p - outbuf) * 4); + +#if 0 + fprintf(stderr, + "%dx%d at %d,%d rle from %d to %d bytes (%f) total %dM\n", + width, height, r[i].x1, r[i].y1, + width * height * 4, (int) (p - outbuf) * 4, + (float) (p - outbuf) / (width * height), + recorder->total / 1024 / 1024); +#endif + } + + pixman_region32_fini(&damage); + recorder->count++; +} + +static void +weston_recorder_create(struct weston_output *output, const char *filename) +{ + struct weston_compositor *compositor = output->compositor; + struct weston_recorder *recorder; + int stride, size; + struct { uint32_t magic, format, width, height; } header; + int do_yflip; + + do_yflip = !!(compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP); + + recorder = malloc(sizeof *recorder); + + stride = output->current_mode->width; + size = stride * 4 * output->current_mode->height; + recorder->frame = zalloc(size); + recorder->rect = malloc(size); + recorder->total = 0; + recorder->count = 0; + recorder->output = output; + + if (do_yflip) + recorder->tmpbuf = NULL; + else + recorder->tmpbuf = malloc(size); + + header.magic = WCAP_HEADER_MAGIC; + + switch (compositor->read_format) { + case PIXMAN_x8r8g8b8: + case PIXMAN_a8r8g8b8: + header.format = WCAP_FORMAT_XRGB8888; + break; + case PIXMAN_a8b8g8r8: + header.format = WCAP_FORMAT_XBGR8888; + break; + default: + weston_log("unknown recorder format\n"); + free(recorder); + return; + } + + recorder->fd = open(filename, + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644); + + if (recorder->fd < 0) { + weston_log("problem opening output file %s: %m\n", filename); + free(recorder); + return; + } + + header.width = output->current_mode->width; + header.height = output->current_mode->height; + recorder->total += write(recorder->fd, &header, sizeof header); + + recorder->frame_listener.notify = weston_recorder_frame_notify; + wl_signal_add(&output->frame_signal, &recorder->frame_listener); + output->disable_planes++; + weston_output_damage(output); +} + +static void +weston_recorder_destroy(struct weston_recorder *recorder) +{ + wl_list_remove(&recorder->frame_listener.link); + close(recorder->fd); + free(recorder->tmpbuf); + free(recorder->frame); + free(recorder->rect); + recorder->output->disable_planes--; + free(recorder); +} + +static void +recorder_binding(struct weston_seat *seat, uint32_t time, uint32_t key, void *data) +{ + struct weston_seat *ws = (struct weston_seat *) seat; + struct weston_compositor *ec = ws->compositor; + struct weston_output *output = + container_of(ec->output_list.next, + struct weston_output, link); + struct wl_listener *listener; + struct weston_recorder *recorder; + static const char filename[] = "capture.wcap"; + + listener = wl_signal_get(&output->frame_signal, + weston_recorder_frame_notify); + if (listener) { + recorder = container_of(listener, struct weston_recorder, + frame_listener); + + weston_log( + "stopping recorder, total file size %dM, %d frames\n", + recorder->total / (1024 * 1024), recorder->count); + + weston_recorder_destroy(recorder); + } else { + weston_log("starting recorder, file %s\n", filename); + weston_recorder_create(output, filename); + } +} + +static void +screenshooter_destroy(struct wl_listener *listener, void *data) +{ + struct screenshooter *shooter = + container_of(listener, struct screenshooter, destroy_listener); + + wl_global_destroy(shooter->global); + free(shooter); +} + +void +screenshooter_create(struct weston_compositor *ec) +{ + struct screenshooter *shooter; + + shooter = malloc(sizeof *shooter); + if (shooter == NULL) + return; + + shooter->ec = ec; + shooter->client = NULL; + + shooter->global = wl_global_create(ec->wl_display, + &screenshooter_interface, 1, + shooter, bind_shooter); + weston_compositor_add_key_binding(ec, KEY_S, MODIFIER_SUPER, + screenshooter_binding, shooter); + weston_compositor_add_key_binding(ec, KEY_R, MODIFIER_SUPER, + recorder_binding, shooter); + + shooter->destroy_listener.notify = screenshooter_destroy; + wl_signal_add(&ec->destroy_signal, &shooter->destroy_listener); +} diff --git a/src/shell.c b/src/shell.c new file mode 100644 index 00000000..fa9ff544 --- /dev/null +++ b/src/shell.c @@ -0,0 +1,4799 @@ +/* + * Copyright © 2010-2012 Intel Corporation + * Copyright © 2011-2012 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compositor.h" +#include "desktop-shell-server-protocol.h" +#include "input-method-server-protocol.h" +#include "workspaces-server-protocol.h" +#include "../shared/config-parser.h" + +#define DEFAULT_NUM_WORKSPACES 1 +#define DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH 200 + +enum animation_type { + ANIMATION_NONE, + + ANIMATION_ZOOM, + ANIMATION_FADE +}; + +enum fade_type { + FADE_IN, + FADE_OUT +}; + +struct focus_state { + struct weston_seat *seat; + struct workspace *ws; + struct weston_surface *keyboard_focus; + struct wl_list link; + struct wl_listener seat_destroy_listener; + struct wl_listener surface_destroy_listener; +}; + +struct workspace { + struct weston_layer layer; + + struct wl_list focus_list; + struct wl_listener seat_destroyed_listener; +}; + +struct input_panel_surface { + struct wl_resource *resource; + struct wl_signal destroy_signal; + + struct desktop_shell *shell; + + struct wl_list link; + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; + + struct weston_output *output; + uint32_t panel; +}; + +struct desktop_shell { + struct weston_compositor *compositor; + + struct wl_listener idle_listener; + struct wl_listener wake_listener; + struct wl_listener destroy_listener; + struct wl_listener show_input_panel_listener; + struct wl_listener hide_input_panel_listener; + struct wl_listener update_input_panel_listener; + + struct weston_layer fullscreen_layer; + struct weston_layer panel_layer; + struct weston_layer background_layer; + struct weston_layer lock_layer; + struct weston_layer input_panel_layer; + + struct wl_listener pointer_focus_listener; + struct weston_surface *grab_surface; + + struct { + struct weston_process process; + struct wl_client *client; + struct wl_resource *desktop_shell; + + unsigned deathcount; + uint32_t deathstamp; + } child; + + bool locked; + bool showing_input_panels; + bool prepare_event_sent; + + struct { + struct weston_surface *surface; + pixman_box32_t cursor_rectangle; + } text_input; + + struct weston_surface *lock_surface; + struct wl_listener lock_surface_listener; + + struct { + struct wl_array array; + unsigned int current; + unsigned int num; + + struct wl_list client_list; + + struct weston_animation animation; + struct wl_list anim_sticky_list; + int anim_dir; + uint32_t anim_timestamp; + double anim_current; + struct workspace *anim_from; + struct workspace *anim_to; + } workspaces; + + struct { + char *path; + int duration; + struct wl_resource *binding; + struct weston_process process; + struct wl_event_source *timer; + } screensaver; + + struct { + struct wl_resource *binding; + struct wl_list surfaces; + } input_panel; + + struct { + struct weston_surface *surface; + struct weston_surface_animation *animation; + enum fade_type type; + struct wl_event_source *startup_timer; + } fade; + + uint32_t binding_modifier; + enum animation_type win_animation_type; + enum animation_type startup_animation_type; +}; + +enum shell_surface_type { + SHELL_SURFACE_NONE, + SHELL_SURFACE_TOPLEVEL, + SHELL_SURFACE_TRANSIENT, + SHELL_SURFACE_FULLSCREEN, + SHELL_SURFACE_MAXIMIZED, + SHELL_SURFACE_POPUP, + SHELL_SURFACE_XWAYLAND +}; + +struct ping_timer { + struct wl_event_source *source; + uint32_t serial; +}; + +struct shell_surface { + struct wl_resource *resource; + struct wl_signal destroy_signal; + + struct weston_surface *surface; + struct wl_listener surface_destroy_listener; + struct weston_surface *parent; + struct desktop_shell *shell; + + enum shell_surface_type type, next_type; + char *title, *class; + int32_t saved_x, saved_y; + bool saved_position_valid; + bool saved_rotation_valid; + int unresponsive; + + struct { + struct weston_transform transform; + struct weston_matrix rotation; + } rotation; + + struct { + struct wl_list grab_link; + int32_t x, y; + struct shell_seat *shseat; + uint32_t serial; + } popup; + + struct { + int32_t x, y; + uint32_t flags; + } transient; + + struct { + enum wl_shell_surface_fullscreen_method type; + struct weston_transform transform; /* matrix from x, y */ + uint32_t framerate; + struct weston_surface *black_surface; + } fullscreen; + + struct ping_timer *ping_timer; + + struct weston_transform workspace_transform; + + struct weston_output *fullscreen_output; + struct weston_output *output; + struct wl_list link; + + const struct weston_shell_client *client; +}; + +struct shell_grab { + struct weston_pointer_grab grab; + struct shell_surface *shsurf; + struct wl_listener shsurf_destroy_listener; +}; + +struct shell_touch_grab { + struct weston_touch_grab grab; + struct shell_surface *shsurf; + struct wl_listener shsurf_destroy_listener; + struct weston_touch *touch; +}; + +struct weston_move_grab { + struct shell_grab base; + wl_fixed_t dx, dy; +}; + +struct weston_touch_move_grab { + struct shell_touch_grab base; + wl_fixed_t dx, dy; +}; + +struct rotate_grab { + struct shell_grab base; + struct weston_matrix rotation; + struct { + float x; + float y; + } center; +}; + +struct shell_seat { + struct weston_seat *seat; + struct wl_listener seat_destroy_listener; + + struct { + struct weston_pointer_grab grab; + struct wl_list surfaces_list; + struct wl_client *client; + int32_t initial_up; + } popup_grab; +}; + +static void +activate(struct desktop_shell *shell, struct weston_surface *es, + struct weston_seat *seat); + +static struct workspace * +get_current_workspace(struct desktop_shell *shell); + +static struct shell_surface * +get_shell_surface(struct weston_surface *surface); + +static struct desktop_shell * +shell_surface_get_shell(struct shell_surface *shsurf); + +static void +surface_rotate(struct shell_surface *surface, struct weston_seat *seat); + +static void +shell_fade_startup(struct desktop_shell *shell); + +static bool +shell_surface_is_top_fullscreen(struct shell_surface *shsurf) +{ + struct desktop_shell *shell; + struct weston_surface *top_fs_es; + + shell = shell_surface_get_shell(shsurf); + + if (wl_list_empty(&shell->fullscreen_layer.surface_list)) + return false; + + top_fs_es = container_of(shell->fullscreen_layer.surface_list.next, + struct weston_surface, + layer_link); + return (shsurf == get_shell_surface(top_fs_es)); +} + +static void +destroy_shell_grab_shsurf(struct wl_listener *listener, void *data) +{ + struct shell_grab *grab; + + grab = container_of(listener, struct shell_grab, + shsurf_destroy_listener); + + grab->shsurf = NULL; +} + +static void +popup_grab_end(struct weston_pointer *pointer); + +static void +shell_grab_start(struct shell_grab *grab, + const struct weston_pointer_grab_interface *interface, + struct shell_surface *shsurf, + struct weston_pointer *pointer, + enum desktop_shell_cursor cursor) +{ + struct desktop_shell *shell = shsurf->shell; + + popup_grab_end(pointer); + + grab->grab.interface = interface; + grab->shsurf = shsurf; + grab->shsurf_destroy_listener.notify = destroy_shell_grab_shsurf; + wl_signal_add(&shsurf->destroy_signal, + &grab->shsurf_destroy_listener); + + weston_pointer_start_grab(pointer, &grab->grab); + if (shell->child.desktop_shell) { + desktop_shell_send_grab_cursor(shell->child.desktop_shell, + cursor); + weston_pointer_set_focus(pointer, shell->grab_surface, + wl_fixed_from_int(0), + wl_fixed_from_int(0)); + } +} + +static void +shell_grab_end(struct shell_grab *grab) +{ + if (grab->shsurf) + wl_list_remove(&grab->shsurf_destroy_listener.link); + + weston_pointer_end_grab(grab->grab.pointer); +} + +static void +shell_touch_grab_start(struct shell_touch_grab *grab, + const struct weston_touch_grab_interface *interface, + struct shell_surface *shsurf, + struct weston_touch *touch) +{ + struct desktop_shell *shell = shsurf->shell; + + grab->grab.interface = interface; + grab->shsurf = shsurf; + grab->shsurf_destroy_listener.notify = destroy_shell_grab_shsurf; + wl_signal_add(&shsurf->destroy_signal, + &grab->shsurf_destroy_listener); + + grab->touch = touch; + + weston_touch_start_grab(touch, &grab->grab); + if (shell->child.desktop_shell) + weston_touch_set_focus(touch->seat, shell->grab_surface); +} + +static void +shell_touch_grab_end(struct shell_touch_grab *grab) +{ + if (grab->shsurf) + wl_list_remove(&grab->shsurf_destroy_listener.link); + + weston_touch_end_grab(grab->touch); +} + +static void +center_on_output(struct weston_surface *surface, + struct weston_output *output); + +static enum weston_keyboard_modifier +get_modifier(char *modifier) +{ + if (!modifier) + return MODIFIER_SUPER; + + if (!strcmp("ctrl", modifier)) + return MODIFIER_CTRL; + else if (!strcmp("alt", modifier)) + return MODIFIER_ALT; + else if (!strcmp("super", modifier)) + return MODIFIER_SUPER; + else + return MODIFIER_SUPER; +} + +static enum animation_type +get_animation_type(char *animation) +{ + if (!strcmp("zoom", animation)) + return ANIMATION_ZOOM; + else if (!strcmp("fade", animation)) + return ANIMATION_FADE; + else + return ANIMATION_NONE; +} + +static void +shell_configuration(struct desktop_shell *shell) +{ + struct weston_config_section *section; + int duration; + char *s; + + section = weston_config_get_section(shell->compositor->config, + "screensaver", NULL, NULL); + weston_config_section_get_string(section, + "path", &shell->screensaver.path, NULL); + weston_config_section_get_int(section, "duration", &duration, 60); + shell->screensaver.duration = duration * 1000; + + section = weston_config_get_section(shell->compositor->config, + "shell", NULL, NULL); + weston_config_section_get_string(section, + "binding-modifier", &s, "super"); + shell->binding_modifier = get_modifier(s); + free(s); + weston_config_section_get_string(section, "animation", &s, "none"); + shell->win_animation_type = get_animation_type(s); + free(s); + weston_config_section_get_string(section, + "startup-animation", &s, "fade"); + shell->startup_animation_type = get_animation_type(s); + free(s); + if (shell->startup_animation_type == ANIMATION_ZOOM) + shell->startup_animation_type = ANIMATION_NONE; + + weston_config_section_get_uint(section, "num-workspaces", + &shell->workspaces.num, + DEFAULT_NUM_WORKSPACES); +} + +static void +focus_state_destroy(struct focus_state *state) +{ + wl_list_remove(&state->seat_destroy_listener.link); + wl_list_remove(&state->surface_destroy_listener.link); + free(state); +} + +static void +focus_state_seat_destroy(struct wl_listener *listener, void *data) +{ + struct focus_state *state = container_of(listener, + struct focus_state, + seat_destroy_listener); + + wl_list_remove(&state->link); + focus_state_destroy(state); +} + +static void +focus_state_surface_destroy(struct wl_listener *listener, void *data) +{ + struct focus_state *state = container_of(listener, + struct focus_state, + surface_destroy_listener); + struct desktop_shell *shell; + struct weston_surface *main_surface; + struct weston_surface *surface, *next; + + main_surface = weston_surface_get_main_surface(state->keyboard_focus); + + next = NULL; + wl_list_for_each(surface, &state->ws->layer.surface_list, layer_link) { + if (surface == main_surface) + continue; + + next = surface; + break; + } + + /* if the focus was a sub-surface, activate its main surface */ + if (main_surface != state->keyboard_focus) + next = main_surface; + + if (next) { + shell = state->seat->compositor->shell_interface.shell; + activate(shell, next, state->seat); + } else { + wl_list_remove(&state->link); + focus_state_destroy(state); + } +} + +static struct focus_state * +focus_state_create(struct weston_seat *seat, struct workspace *ws) +{ + struct focus_state *state; + + state = malloc(sizeof *state); + if (state == NULL) + return NULL; + + state->ws = ws; + state->seat = seat; + wl_list_insert(&ws->focus_list, &state->link); + + state->seat_destroy_listener.notify = focus_state_seat_destroy; + state->surface_destroy_listener.notify = focus_state_surface_destroy; + wl_signal_add(&seat->destroy_signal, + &state->seat_destroy_listener); + wl_list_init(&state->surface_destroy_listener.link); + + return state; +} + +static struct focus_state * +ensure_focus_state(struct desktop_shell *shell, struct weston_seat *seat) +{ + struct workspace *ws = get_current_workspace(shell); + struct focus_state *state; + + wl_list_for_each(state, &ws->focus_list, link) + if (state->seat == seat) + break; + + if (&state->link == &ws->focus_list) + state = focus_state_create(seat, ws); + + return state; +} + +static void +restore_focus_state(struct desktop_shell *shell, struct workspace *ws) +{ + struct focus_state *state, *next; + struct weston_surface *surface; + + wl_list_for_each_safe(state, next, &ws->focus_list, link) { + surface = state->keyboard_focus; + + weston_keyboard_set_focus(state->seat->keyboard, surface); + } +} + +static void +replace_focus_state(struct desktop_shell *shell, struct workspace *ws, + struct weston_seat *seat) +{ + struct focus_state *state; + struct weston_surface *surface; + + wl_list_for_each(state, &ws->focus_list, link) { + if (state->seat == seat) { + surface = seat->keyboard->focus; + state->keyboard_focus = surface; + return; + } + } +} + +static void +drop_focus_state(struct desktop_shell *shell, struct workspace *ws, + struct weston_surface *surface) +{ + struct focus_state *state; + + wl_list_for_each(state, &ws->focus_list, link) + if (state->keyboard_focus == surface) + state->keyboard_focus = NULL; +} + +static void +workspace_destroy(struct workspace *ws) +{ + struct focus_state *state, *next; + + wl_list_for_each_safe(state, next, &ws->focus_list, link) + focus_state_destroy(state); + + free(ws); +} + +static void +seat_destroyed(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = data; + struct focus_state *state, *next; + struct workspace *ws = container_of(listener, + struct workspace, + seat_destroyed_listener); + + wl_list_for_each_safe(state, next, &ws->focus_list, link) + if (state->seat == seat) + wl_list_remove(&state->link); +} + +static struct workspace * +workspace_create(void) +{ + struct workspace *ws = malloc(sizeof *ws); + if (ws == NULL) + return NULL; + + weston_layer_init(&ws->layer, NULL); + + wl_list_init(&ws->focus_list); + wl_list_init(&ws->seat_destroyed_listener.link); + ws->seat_destroyed_listener.notify = seat_destroyed; + + return ws; +} + +static int +workspace_is_empty(struct workspace *ws) +{ + return wl_list_empty(&ws->layer.surface_list); +} + +static struct workspace * +get_workspace(struct desktop_shell *shell, unsigned int index) +{ + struct workspace **pws = shell->workspaces.array.data; + assert(index < shell->workspaces.num); + pws += index; + return *pws; +} + +static struct workspace * +get_current_workspace(struct desktop_shell *shell) +{ + return get_workspace(shell, shell->workspaces.current); +} + +static void +activate_workspace(struct desktop_shell *shell, unsigned int index) +{ + struct workspace *ws; + + ws = get_workspace(shell, index); + wl_list_insert(&shell->panel_layer.link, &ws->layer.link); + + shell->workspaces.current = index; +} + +static unsigned int +get_output_height(struct weston_output *output) +{ + return abs(output->region.extents.y1 - output->region.extents.y2); +} + +static void +surface_translate(struct weston_surface *surface, double d) +{ + struct shell_surface *shsurf = get_shell_surface(surface); + struct weston_transform *transform; + + transform = &shsurf->workspace_transform; + if (wl_list_empty(&transform->link)) + wl_list_insert(surface->geometry.transformation_list.prev, + &shsurf->workspace_transform.link); + + weston_matrix_init(&shsurf->workspace_transform.matrix); + weston_matrix_translate(&shsurf->workspace_transform.matrix, + 0.0, d, 0.0); + weston_surface_geometry_dirty(surface); +} + +static void +workspace_translate_out(struct workspace *ws, double fraction) +{ + struct weston_surface *surface; + unsigned int height; + double d; + + wl_list_for_each(surface, &ws->layer.surface_list, layer_link) { + height = get_output_height(surface->output); + d = height * fraction; + + surface_translate(surface, d); + } +} + +static void +workspace_translate_in(struct workspace *ws, double fraction) +{ + struct weston_surface *surface; + unsigned int height; + double d; + + wl_list_for_each(surface, &ws->layer.surface_list, layer_link) { + height = get_output_height(surface->output); + + if (fraction > 0) + d = -(height - height * fraction); + else + d = height + height * fraction; + + surface_translate(surface, d); + } +} + +static void +broadcast_current_workspace_state(struct desktop_shell *shell) +{ + struct wl_resource *resource; + + wl_resource_for_each(resource, &shell->workspaces.client_list) + workspace_manager_send_state(resource, + shell->workspaces.current, + shell->workspaces.num); +} + +static void +reverse_workspace_change_animation(struct desktop_shell *shell, + unsigned int index, + struct workspace *from, + struct workspace *to) +{ + shell->workspaces.current = index; + + shell->workspaces.anim_to = to; + shell->workspaces.anim_from = from; + shell->workspaces.anim_dir = -1 * shell->workspaces.anim_dir; + shell->workspaces.anim_timestamp = 0; + + weston_compositor_schedule_repaint(shell->compositor); +} + +static void +workspace_deactivate_transforms(struct workspace *ws) +{ + struct weston_surface *surface; + struct shell_surface *shsurf; + + wl_list_for_each(surface, &ws->layer.surface_list, layer_link) { + shsurf = get_shell_surface(surface); + if (!wl_list_empty(&shsurf->workspace_transform.link)) { + wl_list_remove(&shsurf->workspace_transform.link); + wl_list_init(&shsurf->workspace_transform.link); + } + weston_surface_geometry_dirty(surface); + } +} + +static void +finish_workspace_change_animation(struct desktop_shell *shell, + struct workspace *from, + struct workspace *to) +{ + weston_compositor_schedule_repaint(shell->compositor); + + wl_list_remove(&shell->workspaces.animation.link); + workspace_deactivate_transforms(from); + workspace_deactivate_transforms(to); + shell->workspaces.anim_to = NULL; + + wl_list_remove(&shell->workspaces.anim_from->layer.link); +} + +static void +animate_workspace_change_frame(struct weston_animation *animation, + struct weston_output *output, uint32_t msecs) +{ + struct desktop_shell *shell = + container_of(animation, struct desktop_shell, + workspaces.animation); + struct workspace *from = shell->workspaces.anim_from; + struct workspace *to = shell->workspaces.anim_to; + uint32_t t; + double x, y; + + if (workspace_is_empty(from) && workspace_is_empty(to)) { + finish_workspace_change_animation(shell, from, to); + return; + } + + if (shell->workspaces.anim_timestamp == 0) { + if (shell->workspaces.anim_current == 0.0) + shell->workspaces.anim_timestamp = msecs; + else + shell->workspaces.anim_timestamp = + msecs - + /* Invers of movement function 'y' below. */ + (asin(1.0 - shell->workspaces.anim_current) * + DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH * + M_2_PI); + } + + t = msecs - shell->workspaces.anim_timestamp; + + /* + * x = [0, π/2] + * y(x) = sin(x) + */ + x = t * (1.0/DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH) * M_PI_2; + y = sin(x); + + if (t < DEFAULT_WORKSPACE_CHANGE_ANIMATION_LENGTH) { + weston_compositor_schedule_repaint(shell->compositor); + + workspace_translate_out(from, shell->workspaces.anim_dir * y); + workspace_translate_in(to, shell->workspaces.anim_dir * y); + shell->workspaces.anim_current = y; + + weston_compositor_schedule_repaint(shell->compositor); + } + else + finish_workspace_change_animation(shell, from, to); +} + +static void +animate_workspace_change(struct desktop_shell *shell, + unsigned int index, + struct workspace *from, + struct workspace *to) +{ + struct weston_output *output; + + int dir; + + if (index > shell->workspaces.current) + dir = -1; + else + dir = 1; + + shell->workspaces.current = index; + + shell->workspaces.anim_dir = dir; + shell->workspaces.anim_from = from; + shell->workspaces.anim_to = to; + shell->workspaces.anim_current = 0.0; + shell->workspaces.anim_timestamp = 0; + + output = container_of(shell->compositor->output_list.next, + struct weston_output, link); + wl_list_insert(&output->animation_list, + &shell->workspaces.animation.link); + + wl_list_insert(from->layer.link.prev, &to->layer.link); + + workspace_translate_in(to, 0); + + restore_focus_state(shell, to); + + weston_compositor_schedule_repaint(shell->compositor); +} + +static void +update_workspace(struct desktop_shell *shell, unsigned int index, + struct workspace *from, struct workspace *to) +{ + shell->workspaces.current = index; + wl_list_insert(&from->layer.link, &to->layer.link); + wl_list_remove(&from->layer.link); +} + +static void +change_workspace(struct desktop_shell *shell, unsigned int index) +{ + struct workspace *from; + struct workspace *to; + + if (index == shell->workspaces.current) + return; + + /* Don't change workspace when there is any fullscreen surfaces. */ + if (!wl_list_empty(&shell->fullscreen_layer.surface_list)) + return; + + from = get_current_workspace(shell); + to = get_workspace(shell, index); + + if (shell->workspaces.anim_from == to && + shell->workspaces.anim_to == from) { + restore_focus_state(shell, to); + reverse_workspace_change_animation(shell, index, from, to); + broadcast_current_workspace_state(shell); + return; + } + + if (shell->workspaces.anim_to != NULL) + finish_workspace_change_animation(shell, + shell->workspaces.anim_from, + shell->workspaces.anim_to); + + restore_focus_state(shell, to); + + if (workspace_is_empty(to) && workspace_is_empty(from)) + update_workspace(shell, index, from, to); + else + animate_workspace_change(shell, index, from, to); + + broadcast_current_workspace_state(shell); +} + +static bool +workspace_has_only(struct workspace *ws, struct weston_surface *surface) +{ + struct wl_list *list = &ws->layer.surface_list; + struct wl_list *e; + + if (wl_list_empty(list)) + return false; + + e = list->next; + + if (e->next != list) + return false; + + return container_of(e, struct weston_surface, layer_link) == surface; +} + +static void +move_surface_to_workspace(struct desktop_shell *shell, + struct weston_surface *surface, + uint32_t workspace) +{ + struct workspace *from; + struct workspace *to; + struct weston_seat *seat; + struct weston_surface *focus; + + assert(weston_surface_get_main_surface(surface) == surface); + + if (workspace == shell->workspaces.current) + return; + + if (workspace >= shell->workspaces.num) + workspace = shell->workspaces.num - 1; + + from = get_current_workspace(shell); + to = get_workspace(shell, workspace); + + wl_list_remove(&surface->layer_link); + wl_list_insert(&to->layer.surface_list, &surface->layer_link); + + drop_focus_state(shell, from, surface); + wl_list_for_each(seat, &shell->compositor->seat_list, link) { + if (!seat->keyboard) + continue; + + focus = weston_surface_get_main_surface(seat->keyboard->focus); + if (focus == surface) + weston_keyboard_set_focus(seat->keyboard, NULL); + } + + weston_surface_damage_below(surface); +} + +static void +take_surface_to_workspace_by_seat(struct desktop_shell *shell, + struct weston_seat *seat, + unsigned int index) +{ + struct weston_surface *surface; + struct shell_surface *shsurf; + struct workspace *from; + struct workspace *to; + struct focus_state *state; + + surface = weston_surface_get_main_surface(seat->keyboard->focus); + if (surface == NULL || + index == shell->workspaces.current) + return; + + from = get_current_workspace(shell); + to = get_workspace(shell, index); + + wl_list_remove(&surface->layer_link); + wl_list_insert(&to->layer.surface_list, &surface->layer_link); + + replace_focus_state(shell, to, seat); + drop_focus_state(shell, from, surface); + + if (shell->workspaces.anim_from == to && + shell->workspaces.anim_to == from) { + wl_list_remove(&to->layer.link); + wl_list_insert(from->layer.link.prev, &to->layer.link); + + reverse_workspace_change_animation(shell, index, from, to); + broadcast_current_workspace_state(shell); + + return; + } + + if (shell->workspaces.anim_to != NULL) + finish_workspace_change_animation(shell, + shell->workspaces.anim_from, + shell->workspaces.anim_to); + + if (workspace_is_empty(from) && + workspace_has_only(to, surface)) + update_workspace(shell, index, from, to); + else { + shsurf = get_shell_surface(surface); + if (wl_list_empty(&shsurf->workspace_transform.link)) + wl_list_insert(&shell->workspaces.anim_sticky_list, + &shsurf->workspace_transform.link); + + animate_workspace_change(shell, index, from, to); + } + + broadcast_current_workspace_state(shell); + + state = ensure_focus_state(shell, seat); + if (state != NULL) + state->keyboard_focus = surface; +} + +static void +workspace_manager_move_surface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource, + uint32_t workspace) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct weston_surface *main_surface; + + main_surface = weston_surface_get_main_surface(surface); + move_surface_to_workspace(shell, main_surface, workspace); +} + +static const struct workspace_manager_interface workspace_manager_implementation = { + workspace_manager_move_surface, +}; + +static void +unbind_resource(struct wl_resource *resource) +{ + wl_list_remove(wl_resource_get_link(resource)); +} + +static void +bind_workspace_manager(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct desktop_shell *shell = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, + &workspace_manager_interface, 1, id); + + if (resource == NULL) { + weston_log("couldn't add workspace manager object"); + return; + } + + wl_resource_set_implementation(resource, + &workspace_manager_implementation, + shell, unbind_resource); + wl_list_insert(&shell->workspaces.client_list, + wl_resource_get_link(resource)); + + workspace_manager_send_state(resource, + shell->workspaces.current, + shell->workspaces.num); +} + +static void +touch_move_grab_down(struct weston_touch_grab *grab, uint32_t time, + int touch_id, wl_fixed_t sx, wl_fixed_t sy) +{ +} + +static void +touch_move_grab_up(struct weston_touch_grab *grab, uint32_t time, int touch_id) +{ + struct weston_touch_move_grab *move = + (struct weston_touch_move_grab *) container_of( + grab, struct shell_touch_grab, grab); + + if (grab->touch->seat->num_tp == 0) { + shell_touch_grab_end(&move->base); + free(move); + } +} + +static void +touch_move_grab_motion(struct weston_touch_grab *grab, uint32_t time, + int touch_id, wl_fixed_t sx, wl_fixed_t sy) +{ + struct weston_touch_move_grab *move = (struct weston_touch_move_grab *) grab; + struct shell_surface *shsurf = move->base.shsurf; + struct weston_surface *es; + int dx = wl_fixed_to_int(grab->touch->grab_x + move->dx); + int dy = wl_fixed_to_int(grab->touch->grab_y + move->dy); + + if (!shsurf) + return; + + es = shsurf->surface; + + weston_surface_configure(es, dx, dy, + es->geometry.width, es->geometry.height); + + weston_compositor_schedule_repaint(es->compositor); +} + +static void +touch_move_grab_cancel(struct weston_touch_grab *grab) +{ + struct weston_touch_move_grab *move = + (struct weston_touch_move_grab *) container_of( + grab, struct shell_touch_grab, grab); + + shell_touch_grab_end(&move->base); + free(move); +} + +static const struct weston_touch_grab_interface touch_move_grab_interface = { + touch_move_grab_down, + touch_move_grab_up, + touch_move_grab_motion, + touch_move_grab_cancel, +}; + +static int +surface_touch_move(struct shell_surface *shsurf, struct weston_seat *seat) +{ + struct weston_touch_move_grab *move; + + if (!shsurf) + return -1; + + if (shsurf->type == SHELL_SURFACE_FULLSCREEN) + return 0; + + move = malloc(sizeof *move); + if (!move) + return -1; + + move->dx = wl_fixed_from_double(shsurf->surface->geometry.x) - + seat->touch->grab_x; + move->dy = wl_fixed_from_double(shsurf->surface->geometry.y) - + seat->touch->grab_y; + + shell_touch_grab_start(&move->base, &touch_move_grab_interface, shsurf, + seat->touch); + + return 0; +} + +static void +noop_grab_focus(struct weston_pointer_grab *grab) +{ +} + +static void +move_grab_motion(struct weston_pointer_grab *grab, uint32_t time) +{ + struct weston_move_grab *move = (struct weston_move_grab *) grab; + struct weston_pointer *pointer = grab->pointer; + struct shell_surface *shsurf = move->base.shsurf; + struct weston_surface *es; + int dx = wl_fixed_to_int(pointer->x + move->dx); + int dy = wl_fixed_to_int(pointer->y + move->dy); + + if (!shsurf) + return; + + es = shsurf->surface; + + weston_surface_configure(es, dx, dy, + es->geometry.width, es->geometry.height); + + weston_compositor_schedule_repaint(es->compositor); +} + +static void +move_grab_button(struct weston_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state_w) +{ + struct shell_grab *shell_grab = container_of(grab, struct shell_grab, + grab); + struct weston_pointer *pointer = grab->pointer; + enum wl_pointer_button_state state = state_w; + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + shell_grab_end(shell_grab); + free(grab); + } +} + +static void +move_grab_cancel(struct weston_pointer_grab *grab) +{ + struct shell_grab *shell_grab = + container_of(grab, struct shell_grab, grab); + + shell_grab_end(shell_grab); + free(grab); +} + +static const struct weston_pointer_grab_interface move_grab_interface = { + noop_grab_focus, + move_grab_motion, + move_grab_button, + move_grab_cancel, +}; + +static int +surface_move(struct shell_surface *shsurf, struct weston_seat *seat) +{ + struct weston_move_grab *move; + + if (!shsurf) + return -1; + + if (shsurf->type == SHELL_SURFACE_FULLSCREEN) + return 0; + + move = malloc(sizeof *move); + if (!move) + return -1; + + move->dx = wl_fixed_from_double(shsurf->surface->geometry.x) - + seat->pointer->grab_x; + move->dy = wl_fixed_from_double(shsurf->surface->geometry.y) - + seat->pointer->grab_y; + + shell_grab_start(&move->base, &move_grab_interface, shsurf, + seat->pointer, DESKTOP_SHELL_CURSOR_MOVE); + + return 0; +} + +static void +shell_surface_move(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *seat_resource, uint32_t serial) +{ + struct weston_seat *seat = wl_resource_get_user_data(seat_resource); + struct shell_surface *shsurf = wl_resource_get_user_data(resource); + struct weston_surface *surface; + + if (seat->pointer && + seat->pointer->button_count > 0 && + seat->pointer->grab_serial == serial) { + surface = weston_surface_get_main_surface(seat->pointer->focus); + if ((surface == shsurf->surface) && + (surface_move(shsurf, seat) < 0)) + wl_resource_post_no_memory(resource); + } else if (seat->touch && + seat->touch->grab_serial == serial) { + surface = weston_surface_get_main_surface(seat->touch->focus); + if ((surface == shsurf->surface) && + (surface_touch_move(shsurf, seat) < 0)) + wl_resource_post_no_memory(resource); + } +} + +struct weston_resize_grab { + struct shell_grab base; + uint32_t edges; + int32_t width, height; +}; + +static void +resize_grab_motion(struct weston_pointer_grab *grab, uint32_t time) +{ + struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; + struct weston_pointer *pointer = grab->pointer; + struct shell_surface *shsurf = resize->base.shsurf; + int32_t width, height; + wl_fixed_t from_x, from_y; + wl_fixed_t to_x, to_y; + + if (!shsurf) + return; + + weston_surface_from_global_fixed(shsurf->surface, + pointer->grab_x, pointer->grab_y, + &from_x, &from_y); + weston_surface_from_global_fixed(shsurf->surface, + pointer->x, pointer->y, &to_x, &to_y); + + width = resize->width; + if (resize->edges & WL_SHELL_SURFACE_RESIZE_LEFT) { + width += wl_fixed_to_int(from_x - to_x); + } else if (resize->edges & WL_SHELL_SURFACE_RESIZE_RIGHT) { + width += wl_fixed_to_int(to_x - from_x); + } + + height = resize->height; + if (resize->edges & WL_SHELL_SURFACE_RESIZE_TOP) { + height += wl_fixed_to_int(from_y - to_y); + } else if (resize->edges & WL_SHELL_SURFACE_RESIZE_BOTTOM) { + height += wl_fixed_to_int(to_y - from_y); + } + + shsurf->client->send_configure(shsurf->surface, + resize->edges, width, height); +} + +static void +send_configure(struct weston_surface *surface, + uint32_t edges, int32_t width, int32_t height) +{ + struct shell_surface *shsurf = get_shell_surface(surface); + + wl_shell_surface_send_configure(shsurf->resource, + edges, width, height); +} + +static const struct weston_shell_client shell_client = { + send_configure +}; + +static void +resize_grab_button(struct weston_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state_w) +{ + struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; + struct weston_pointer *pointer = grab->pointer; + enum wl_pointer_button_state state = state_w; + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + shell_grab_end(&resize->base); + free(grab); + } +} + +static void +resize_grab_cancel(struct weston_pointer_grab *grab) +{ + struct weston_resize_grab *resize = (struct weston_resize_grab *) grab; + + shell_grab_end(&resize->base); + free(grab); +} + +static const struct weston_pointer_grab_interface resize_grab_interface = { + noop_grab_focus, + resize_grab_motion, + resize_grab_button, + resize_grab_cancel, +}; + +/* + * Returns the bounding box of a surface and all its sub-surfaces, + * in the surface coordinates system. */ +static void +surface_subsurfaces_boundingbox(struct weston_surface *surface, int32_t *x, + int32_t *y, int32_t *w, int32_t *h) { + pixman_region32_t region; + pixman_box32_t *box; + struct weston_subsurface *subsurface; + + pixman_region32_init_rect(®ion, 0, 0, + surface->geometry.width, + surface->geometry.height); + + wl_list_for_each(subsurface, &surface->subsurface_list, parent_link) { + pixman_region32_union_rect(®ion, ®ion, + subsurface->position.x, + subsurface->position.y, + subsurface->surface->geometry.width, + subsurface->surface->geometry.height); + } + + box = pixman_region32_extents(®ion); + if (x) + *x = box->x1; + if (y) + *y = box->y1; + if (w) + *w = box->x2 - box->x1; + if (h) + *h = box->y2 - box->y1; + + pixman_region32_fini(®ion); +} + +static int +surface_resize(struct shell_surface *shsurf, + struct weston_seat *seat, uint32_t edges) +{ + struct weston_resize_grab *resize; + + if (shsurf->type == SHELL_SURFACE_FULLSCREEN || + shsurf->type == SHELL_SURFACE_MAXIMIZED) + return 0; + + if (edges == 0 || edges > 15 || + (edges & 3) == 3 || (edges & 12) == 12) + return 0; + + resize = malloc(sizeof *resize); + if (!resize) + return -1; + + resize->edges = edges; + surface_subsurfaces_boundingbox(shsurf->surface, NULL, NULL, + &resize->width, &resize->height); + + shell_grab_start(&resize->base, &resize_grab_interface, shsurf, + seat->pointer, edges); + + return 0; +} + +static void +shell_surface_resize(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *seat_resource, uint32_t serial, + uint32_t edges) +{ + struct weston_seat *seat = wl_resource_get_user_data(seat_resource); + struct shell_surface *shsurf = wl_resource_get_user_data(resource); + struct weston_surface *surface; + + if (shsurf->type == SHELL_SURFACE_FULLSCREEN) + return; + + surface = weston_surface_get_main_surface(seat->pointer->focus); + if (seat->pointer->button_count == 0 || + seat->pointer->grab_serial != serial || + surface != shsurf->surface) + return; + + if (surface_resize(shsurf, seat, edges) < 0) + wl_resource_post_no_memory(resource); +} + +static void +end_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer); + +static void +busy_cursor_grab_focus(struct weston_pointer_grab *base) +{ + struct shell_grab *grab = (struct shell_grab *) base; + struct weston_pointer *pointer = base->pointer; + struct weston_surface *surface; + wl_fixed_t sx, sy; + + surface = weston_compositor_pick_surface(pointer->seat->compositor, + pointer->x, pointer->y, + &sx, &sy); + + if (!grab->shsurf || grab->shsurf->surface != surface) + end_busy_cursor(grab->shsurf, pointer); +} + +static void +busy_cursor_grab_motion(struct weston_pointer_grab *grab, uint32_t time) +{ +} + +static void +busy_cursor_grab_button(struct weston_pointer_grab *base, + uint32_t time, uint32_t button, uint32_t state) +{ + struct shell_grab *grab = (struct shell_grab *) base; + struct shell_surface *shsurf = grab->shsurf; + struct weston_seat *seat = grab->grab.pointer->seat; + + if (shsurf && button == BTN_LEFT && state) { + activate(shsurf->shell, shsurf->surface, seat); + surface_move(shsurf, seat); + } else if (shsurf && button == BTN_RIGHT && state) { + activate(shsurf->shell, shsurf->surface, seat); + surface_rotate(shsurf, seat); + } +} + +static void +busy_cursor_grab_cancel(struct weston_pointer_grab *base) +{ + struct shell_grab *grab = (struct shell_grab *) base; + + shell_grab_end(grab); + free(grab); +} + +static const struct weston_pointer_grab_interface busy_cursor_grab_interface = { + busy_cursor_grab_focus, + busy_cursor_grab_motion, + busy_cursor_grab_button, + busy_cursor_grab_cancel, +}; + +static void +set_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer) +{ + struct shell_grab *grab; + + grab = malloc(sizeof *grab); + if (!grab) + return; + + shell_grab_start(grab, &busy_cursor_grab_interface, shsurf, pointer, + DESKTOP_SHELL_CURSOR_BUSY); +} + +static void +end_busy_cursor(struct shell_surface *shsurf, struct weston_pointer *pointer) +{ + struct shell_grab *grab = (struct shell_grab *) pointer->grab; + + if (grab->grab.interface == &busy_cursor_grab_interface && + grab->shsurf == shsurf) { + shell_grab_end(grab); + free(grab); + } +} + +static void +ping_timer_destroy(struct shell_surface *shsurf) +{ + if (!shsurf || !shsurf->ping_timer) + return; + + if (shsurf->ping_timer->source) + wl_event_source_remove(shsurf->ping_timer->source); + + free(shsurf->ping_timer); + shsurf->ping_timer = NULL; +} + +static int +ping_timeout_handler(void *data) +{ + struct shell_surface *shsurf = data; + struct weston_seat *seat; + + /* Client is not responding */ + shsurf->unresponsive = 1; + + wl_list_for_each(seat, &shsurf->surface->compositor->seat_list, link) + if (seat->pointer->focus == shsurf->surface) + set_busy_cursor(shsurf, seat->pointer); + + return 1; +} + +static void +ping_handler(struct weston_surface *surface, uint32_t serial) +{ + struct shell_surface *shsurf = get_shell_surface(surface); + struct wl_event_loop *loop; + int ping_timeout = 200; + + if (!shsurf) + return; + if (!shsurf->resource) + return; + + if (shsurf->surface == shsurf->shell->grab_surface) + return; + + if (!shsurf->ping_timer) { + shsurf->ping_timer = malloc(sizeof *shsurf->ping_timer); + if (!shsurf->ping_timer) + return; + + shsurf->ping_timer->serial = serial; + loop = wl_display_get_event_loop(surface->compositor->wl_display); + shsurf->ping_timer->source = + wl_event_loop_add_timer(loop, ping_timeout_handler, shsurf); + wl_event_source_timer_update(shsurf->ping_timer->source, ping_timeout); + + wl_shell_surface_send_ping(shsurf->resource, serial); + } +} + +static void +handle_pointer_focus(struct wl_listener *listener, void *data) +{ + struct weston_pointer *pointer = data; + struct weston_surface *surface = pointer->focus; + struct weston_compositor *compositor; + struct shell_surface *shsurf; + uint32_t serial; + + if (!surface) + return; + + compositor = surface->compositor; + shsurf = get_shell_surface(surface); + + if (shsurf && shsurf->unresponsive) { + set_busy_cursor(shsurf, pointer); + } else { + serial = wl_display_next_serial(compositor->wl_display); + ping_handler(surface, serial); + } +} + +static void +create_pointer_focus_listener(struct weston_seat *seat) +{ + struct wl_listener *listener; + + if (!seat->pointer) + return; + + listener = malloc(sizeof *listener); + listener->notify = handle_pointer_focus; + wl_signal_add(&seat->pointer->focus_signal, listener); +} + +static void +shell_surface_pong(struct wl_client *client, struct wl_resource *resource, + uint32_t serial) +{ + struct shell_surface *shsurf = wl_resource_get_user_data(resource); + struct weston_seat *seat; + struct weston_compositor *ec = shsurf->surface->compositor; + + if (shsurf->ping_timer == NULL) + /* Just ignore unsolicited pong. */ + return; + + if (shsurf->ping_timer->serial == serial) { + shsurf->unresponsive = 0; + wl_list_for_each(seat, &ec->seat_list, link) { + if(seat->pointer) + end_busy_cursor(shsurf, seat->pointer); + } + ping_timer_destroy(shsurf); + } +} + +static void +set_title(struct shell_surface *shsurf, const char *title) +{ + free(shsurf->title); + shsurf->title = strdup(title); +} + +static void +shell_surface_set_title(struct wl_client *client, + struct wl_resource *resource, const char *title) +{ + struct shell_surface *shsurf = wl_resource_get_user_data(resource); + + set_title(shsurf, title); +} + +static void +shell_surface_set_class(struct wl_client *client, + struct wl_resource *resource, const char *class) +{ + struct shell_surface *shsurf = wl_resource_get_user_data(resource); + + free(shsurf->class); + shsurf->class = strdup(class); +} + +static struct weston_output * +get_default_output(struct weston_compositor *compositor) +{ + return container_of(compositor->output_list.next, + struct weston_output, link); +} + +static void +restore_output_mode(struct weston_output *output) +{ + if (output->current_mode != output->original_mode || + (int32_t)output->current_scale != output->original_scale) + weston_output_switch_mode(output, + output->original_mode, + output->original_scale, + WESTON_MODE_SWITCH_RESTORE_NATIVE); +} + +static void +restore_all_output_modes(struct weston_compositor *compositor) +{ + struct weston_output *output; + + wl_list_for_each(output, &compositor->output_list, link) + restore_output_mode(output); +} + +static void +shell_unset_fullscreen(struct shell_surface *shsurf) +{ + struct workspace *ws; + /* undo all fullscreen things here */ + if (shsurf->fullscreen.type == WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER && + shell_surface_is_top_fullscreen(shsurf)) { + restore_output_mode(shsurf->fullscreen_output); + } + shsurf->fullscreen.type = WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT; + shsurf->fullscreen.framerate = 0; + wl_list_remove(&shsurf->fullscreen.transform.link); + wl_list_init(&shsurf->fullscreen.transform.link); + if (shsurf->fullscreen.black_surface) + weston_surface_destroy(shsurf->fullscreen.black_surface); + shsurf->fullscreen.black_surface = NULL; + shsurf->fullscreen_output = NULL; + weston_surface_set_position(shsurf->surface, + shsurf->saved_x, shsurf->saved_y); + if (shsurf->saved_rotation_valid) { + wl_list_insert(&shsurf->surface->geometry.transformation_list, + &shsurf->rotation.transform.link); + shsurf->saved_rotation_valid = false; + } + + ws = get_current_workspace(shsurf->shell); + wl_list_remove(&shsurf->surface->layer_link); + wl_list_insert(&ws->layer.surface_list, &shsurf->surface->layer_link); +} + +static void +shell_unset_maximized(struct shell_surface *shsurf) +{ + struct workspace *ws; + /* undo all maximized things here */ + shsurf->output = get_default_output(shsurf->surface->compositor); + weston_surface_set_position(shsurf->surface, + shsurf->saved_x, + shsurf->saved_y); + + if (shsurf->saved_rotation_valid) { + wl_list_insert(&shsurf->surface->geometry.transformation_list, + &shsurf->rotation.transform.link); + shsurf->saved_rotation_valid = false; + } + + ws = get_current_workspace(shsurf->shell); + wl_list_remove(&shsurf->surface->layer_link); + wl_list_insert(&ws->layer.surface_list, &shsurf->surface->layer_link); +} + +static int +reset_shell_surface_type(struct shell_surface *surface) +{ + switch (surface->type) { + case SHELL_SURFACE_FULLSCREEN: + shell_unset_fullscreen(surface); + break; + case SHELL_SURFACE_MAXIMIZED: + shell_unset_maximized(surface); + break; + case SHELL_SURFACE_NONE: + case SHELL_SURFACE_TOPLEVEL: + case SHELL_SURFACE_TRANSIENT: + case SHELL_SURFACE_POPUP: + case SHELL_SURFACE_XWAYLAND: + break; + } + + surface->type = SHELL_SURFACE_NONE; + return 0; +} + +static void +set_surface_type(struct shell_surface *shsurf) +{ + struct weston_surface *surface = shsurf->surface; + struct weston_surface *pes = shsurf->parent; + + reset_shell_surface_type(shsurf); + + shsurf->type = shsurf->next_type; + shsurf->next_type = SHELL_SURFACE_NONE; + + switch (shsurf->type) { + case SHELL_SURFACE_TOPLEVEL: + break; + case SHELL_SURFACE_TRANSIENT: + weston_surface_set_position(surface, + pes->geometry.x + shsurf->transient.x, + pes->geometry.y + shsurf->transient.y); + break; + + case SHELL_SURFACE_MAXIMIZED: + case SHELL_SURFACE_FULLSCREEN: + shsurf->saved_x = surface->geometry.x; + shsurf->saved_y = surface->geometry.y; + shsurf->saved_position_valid = true; + + if (!wl_list_empty(&shsurf->rotation.transform.link)) { + wl_list_remove(&shsurf->rotation.transform.link); + wl_list_init(&shsurf->rotation.transform.link); + weston_surface_geometry_dirty(shsurf->surface); + shsurf->saved_rotation_valid = true; + } + break; + + case SHELL_SURFACE_XWAYLAND: + weston_surface_set_position(surface, shsurf->transient.x, + shsurf->transient.y); + break; + + default: + break; + } +} + +static void +set_toplevel(struct shell_surface *shsurf) +{ + shsurf->next_type = SHELL_SURFACE_TOPLEVEL; +} + +static void +shell_surface_set_toplevel(struct wl_client *client, + struct wl_resource *resource) +{ + struct shell_surface *surface = wl_resource_get_user_data(resource); + + set_toplevel(surface); +} + +static void +set_transient(struct shell_surface *shsurf, + struct weston_surface *parent, int x, int y, uint32_t flags) +{ + /* assign to parents output */ + shsurf->parent = parent; + shsurf->transient.x = x; + shsurf->transient.y = y; + shsurf->transient.flags = flags; + shsurf->next_type = SHELL_SURFACE_TRANSIENT; +} + +static void +shell_surface_set_transient(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *parent_resource, + int x, int y, uint32_t flags) +{ + struct shell_surface *shsurf = wl_resource_get_user_data(resource); + struct weston_surface *parent = + wl_resource_get_user_data(parent_resource); + + set_transient(shsurf, parent, x, y, flags); +} + +static struct desktop_shell * +shell_surface_get_shell(struct shell_surface *shsurf) +{ + return shsurf->shell; +} + +static int +get_output_panel_height(struct desktop_shell *shell, + struct weston_output *output) +{ + struct weston_surface *surface; + int panel_height = 0; + + if (!output) + return 0; + + wl_list_for_each(surface, &shell->panel_layer.surface_list, layer_link) { + if (surface->output == output) { + panel_height = surface->geometry.height; + break; + } + } + + return panel_height; +} + +static void +shell_surface_set_maximized(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output_resource ) +{ + struct shell_surface *shsurf = wl_resource_get_user_data(resource); + struct weston_surface *es = shsurf->surface; + struct desktop_shell *shell = NULL; + uint32_t edges = 0, panel_height = 0; + + /* get the default output, if the client set it as NULL + check whether the ouput is available */ + if (output_resource) + shsurf->output = wl_resource_get_user_data(output_resource); + else if (es->output) + shsurf->output = es->output; + else + shsurf->output = get_default_output(es->compositor); + + shell = shell_surface_get_shell(shsurf); + panel_height = get_output_panel_height(shell, shsurf->output); + edges = WL_SHELL_SURFACE_RESIZE_TOP|WL_SHELL_SURFACE_RESIZE_LEFT; + + shsurf->client->send_configure(shsurf->surface, edges, + shsurf->output->width, + shsurf->output->height - panel_height); + + shsurf->next_type = SHELL_SURFACE_MAXIMIZED; +} + +static void +black_surface_configure(struct weston_surface *es, int32_t sx, int32_t sy, int32_t width, int32_t height); + +static struct weston_surface * +create_black_surface(struct weston_compositor *ec, + struct weston_surface *fs_surface, + float x, float y, int w, int h) +{ + struct weston_surface *surface = NULL; + + surface = weston_surface_create(ec); + if (surface == NULL) { + weston_log("no memory\n"); + return NULL; + } + + surface->configure = black_surface_configure; + surface->configure_private = fs_surface; + weston_surface_configure(surface, x, y, w, h); + weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1); + pixman_region32_fini(&surface->opaque); + pixman_region32_init_rect(&surface->opaque, 0, 0, w, h); + pixman_region32_fini(&surface->input); + pixman_region32_init_rect(&surface->input, 0, 0, w, h); + + return surface; +} + +/* Create black surface and append it to the associated fullscreen surface. + * Handle size dismatch and positioning according to the method. */ +static void +shell_configure_fullscreen(struct shell_surface *shsurf) +{ + struct weston_output *output = shsurf->fullscreen_output; + struct weston_surface *surface = shsurf->surface; + struct weston_matrix *matrix; + float scale, output_aspect, surface_aspect, x, y; + int32_t surf_x, surf_y, surf_width, surf_height; + + if (shsurf->fullscreen.type != WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER) + restore_output_mode(output); + + if (!shsurf->fullscreen.black_surface) + shsurf->fullscreen.black_surface = + create_black_surface(surface->compositor, + surface, + output->x, output->y, + output->width, + output->height); + + wl_list_remove(&shsurf->fullscreen.black_surface->layer_link); + wl_list_insert(&surface->layer_link, + &shsurf->fullscreen.black_surface->layer_link); + shsurf->fullscreen.black_surface->output = output; + + surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, + &surf_width, &surf_height); + + switch (shsurf->fullscreen.type) { + case WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT: + if (surface->buffer_ref.buffer) + center_on_output(surface, shsurf->fullscreen_output); + break; + case WL_SHELL_SURFACE_FULLSCREEN_METHOD_SCALE: + /* 1:1 mapping between surface and output dimensions */ + if (output->width == surf_width && + output->height == surf_height) { + weston_surface_set_position(surface, output->x - surf_x, + output->y - surf_y); + break; + } + + matrix = &shsurf->fullscreen.transform.matrix; + weston_matrix_init(matrix); + + output_aspect = (float) output->width / + (float) output->height; + surface_aspect = (float) surface->geometry.width / + (float) surface->geometry.height; + if (output_aspect < surface_aspect) + scale = (float) output->width / + (float) surf_width; + else + scale = (float) output->height / + (float) surf_height; + + weston_matrix_scale(matrix, scale, scale, 1); + wl_list_remove(&shsurf->fullscreen.transform.link); + wl_list_insert(&surface->geometry.transformation_list, + &shsurf->fullscreen.transform.link); + x = output->x + (output->width - surf_width * scale) / 2 - surf_x; + y = output->y + (output->height - surf_height * scale) / 2 - surf_y; + weston_surface_set_position(surface, x, y); + + break; + case WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER: + if (shell_surface_is_top_fullscreen(shsurf)) { + struct weston_mode mode = {0, + surf_width * surface->buffer_scale, + surf_height * surface->buffer_scale, + shsurf->fullscreen.framerate}; + + if (weston_output_switch_mode(output, &mode, surface->buffer_scale, + WESTON_MODE_SWITCH_SET_TEMPORARY) == 0) { + weston_surface_set_position(surface, + output->x - surf_x, + output->y - surf_y); + weston_surface_configure(shsurf->fullscreen.black_surface, + output->x - surf_x, + output->y - surf_y, + output->width, + output->height); + break; + } else { + restore_output_mode(output); + center_on_output(surface, output); + } + } + break; + case WL_SHELL_SURFACE_FULLSCREEN_METHOD_FILL: + center_on_output(surface, output); + break; + default: + break; + } +} + +/* make the fullscreen and black surface at the top */ +static void +shell_stack_fullscreen(struct shell_surface *shsurf) +{ + struct weston_output *output = shsurf->fullscreen_output; + struct weston_surface *surface = shsurf->surface; + struct desktop_shell *shell = shell_surface_get_shell(shsurf); + + wl_list_remove(&surface->layer_link); + wl_list_insert(&shell->fullscreen_layer.surface_list, + &surface->layer_link); + weston_surface_damage(surface); + + if (!shsurf->fullscreen.black_surface) + shsurf->fullscreen.black_surface = + create_black_surface(surface->compositor, + surface, + output->x, output->y, + output->width, + output->height); + + wl_list_remove(&shsurf->fullscreen.black_surface->layer_link); + wl_list_insert(&surface->layer_link, + &shsurf->fullscreen.black_surface->layer_link); + weston_surface_damage(shsurf->fullscreen.black_surface); +} + +static void +shell_map_fullscreen(struct shell_surface *shsurf) +{ + shell_stack_fullscreen(shsurf); + shell_configure_fullscreen(shsurf); +} + +static void +set_fullscreen(struct shell_surface *shsurf, + uint32_t method, + uint32_t framerate, + struct weston_output *output) +{ + struct weston_surface *es = shsurf->surface; + + if (output) + shsurf->output = output; + else if (es->output) + shsurf->output = es->output; + else + shsurf->output = get_default_output(es->compositor); + + shsurf->fullscreen_output = shsurf->output; + shsurf->fullscreen.type = method; + shsurf->fullscreen.framerate = framerate; + shsurf->next_type = SHELL_SURFACE_FULLSCREEN; + + shsurf->client->send_configure(shsurf->surface, 0, + shsurf->output->width, + shsurf->output->height); +} + +static void +shell_surface_set_fullscreen(struct wl_client *client, + struct wl_resource *resource, + uint32_t method, + uint32_t framerate, + struct wl_resource *output_resource) +{ + struct shell_surface *shsurf = wl_resource_get_user_data(resource); + struct weston_output *output; + + if (output_resource) + output = wl_resource_get_user_data(output_resource); + else + output = NULL; + + set_fullscreen(shsurf, method, framerate, output); +} + +static void +set_xwayland(struct shell_surface *shsurf, int x, int y, uint32_t flags) +{ + /* XXX: using the same fields for transient type */ + shsurf->transient.x = x; + shsurf->transient.y = y; + shsurf->transient.flags = flags; + shsurf->next_type = SHELL_SURFACE_XWAYLAND; +} + +static const struct weston_pointer_grab_interface popup_grab_interface; + +static void +destroy_shell_seat(struct wl_listener *listener, void *data) +{ + struct shell_seat *shseat = + container_of(listener, + struct shell_seat, seat_destroy_listener); + struct shell_surface *shsurf, *prev = NULL; + + if (shseat->popup_grab.grab.interface == &popup_grab_interface) { + weston_pointer_end_grab(shseat->popup_grab.grab.pointer); + shseat->popup_grab.client = NULL; + + wl_list_for_each(shsurf, &shseat->popup_grab.surfaces_list, popup.grab_link) { + shsurf->popup.shseat = NULL; + if (prev) { + wl_list_init(&prev->popup.grab_link); + } + prev = shsurf; + } + wl_list_init(&prev->popup.grab_link); + } + + wl_list_remove(&shseat->seat_destroy_listener.link); + free(shseat); +} + +static struct shell_seat * +create_shell_seat(struct weston_seat *seat) +{ + struct shell_seat *shseat; + + shseat = calloc(1, sizeof *shseat); + if (!shseat) { + weston_log("no memory to allocate shell seat\n"); + return NULL; + } + + shseat->seat = seat; + wl_list_init(&shseat->popup_grab.surfaces_list); + + shseat->seat_destroy_listener.notify = destroy_shell_seat; + wl_signal_add(&seat->destroy_signal, + &shseat->seat_destroy_listener); + + return shseat; +} + +static struct shell_seat * +get_shell_seat(struct weston_seat *seat) +{ + struct wl_listener *listener; + + listener = wl_signal_get(&seat->destroy_signal, destroy_shell_seat); + if (listener == NULL) + return create_shell_seat(seat); + + return container_of(listener, + struct shell_seat, seat_destroy_listener); +} + +static void +popup_grab_focus(struct weston_pointer_grab *grab) +{ + struct weston_pointer *pointer = grab->pointer; + struct weston_surface *surface; + struct shell_seat *shseat = + container_of(grab, struct shell_seat, popup_grab.grab); + struct wl_client *client = shseat->popup_grab.client; + wl_fixed_t sx, sy; + + surface = weston_compositor_pick_surface(pointer->seat->compositor, + pointer->x, pointer->y, + &sx, &sy); + + if (surface && wl_resource_get_client(surface->resource) == client) { + weston_pointer_set_focus(pointer, surface, sx, sy); + } else { + weston_pointer_set_focus(pointer, NULL, + wl_fixed_from_int(0), + wl_fixed_from_int(0)); + } +} + +static void +popup_grab_motion(struct weston_pointer_grab *grab, uint32_t time) +{ + struct weston_pointer *pointer = grab->pointer; + struct wl_resource *resource; + wl_fixed_t sx, sy; + + wl_resource_for_each(resource, &pointer->focus_resource_list) { + weston_surface_from_global_fixed(pointer->focus, + pointer->x, pointer->y, + &sx, &sy); + wl_pointer_send_motion(resource, time, sx, sy); + } +} + +static void +popup_grab_button(struct weston_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state_w) +{ + struct wl_resource *resource; + struct shell_seat *shseat = + container_of(grab, struct shell_seat, popup_grab.grab); + struct wl_display *display = shseat->seat->compositor->wl_display; + enum wl_pointer_button_state state = state_w; + uint32_t serial; + struct wl_list *resource_list; + + resource_list = &grab->pointer->focus_resource_list; + if (!wl_list_empty(resource_list)) { + serial = wl_display_get_serial(display); + wl_resource_for_each(resource, resource_list) { + wl_pointer_send_button(resource, serial, + time, button, state); + } + } else if (state == WL_POINTER_BUTTON_STATE_RELEASED && + (shseat->popup_grab.initial_up || + time - shseat->seat->pointer->grab_time > 500)) { + popup_grab_end(grab->pointer); + } + + if (state == WL_POINTER_BUTTON_STATE_RELEASED) + shseat->popup_grab.initial_up = 1; +} + +static void +popup_grab_cancel(struct weston_pointer_grab *grab) +{ + popup_grab_end(grab->pointer); +} + +static const struct weston_pointer_grab_interface popup_grab_interface = { + popup_grab_focus, + popup_grab_motion, + popup_grab_button, + popup_grab_cancel, +}; + +static void +popup_grab_end(struct weston_pointer *pointer) +{ + struct weston_pointer_grab *grab = pointer->grab; + struct shell_seat *shseat = + container_of(grab, struct shell_seat, popup_grab.grab); + struct shell_surface *shsurf; + struct shell_surface *prev = NULL; + + if (pointer->grab->interface == &popup_grab_interface) { + weston_pointer_end_grab(grab->pointer); + shseat->popup_grab.client = NULL; + shseat->popup_grab.grab.interface = NULL; + assert(!wl_list_empty(&shseat->popup_grab.surfaces_list)); + /* Send the popup_done event to all the popups open */ + wl_list_for_each(shsurf, &shseat->popup_grab.surfaces_list, popup.grab_link) { + wl_shell_surface_send_popup_done(shsurf->resource); + shsurf->popup.shseat = NULL; + if (prev) { + wl_list_init(&prev->popup.grab_link); + } + prev = shsurf; + } + wl_list_init(&prev->popup.grab_link); + wl_list_init(&shseat->popup_grab.surfaces_list); + } +} + +static void +add_popup_grab(struct shell_surface *shsurf, struct shell_seat *shseat) +{ + struct weston_seat *seat = shseat->seat; + + if (wl_list_empty(&shseat->popup_grab.surfaces_list)) { + shseat->popup_grab.client = wl_resource_get_client(shsurf->resource); + shseat->popup_grab.grab.interface = &popup_grab_interface; + /* We must make sure here that this popup was opened after + * a mouse press, and not just by moving around with other + * popups already open. */ + if (shseat->seat->pointer->button_count > 0) + shseat->popup_grab.initial_up = 0; + + wl_list_insert(&shseat->popup_grab.surfaces_list, &shsurf->popup.grab_link); + weston_pointer_start_grab(seat->pointer, &shseat->popup_grab.grab); + } else { + wl_list_insert(&shseat->popup_grab.surfaces_list, &shsurf->popup.grab_link); + } +} + +static void +remove_popup_grab(struct shell_surface *shsurf) +{ + struct shell_seat *shseat = shsurf->popup.shseat; + + wl_list_remove(&shsurf->popup.grab_link); + wl_list_init(&shsurf->popup.grab_link); + if (wl_list_empty(&shseat->popup_grab.surfaces_list)) { + weston_pointer_end_grab(shseat->popup_grab.grab.pointer); + shseat->popup_grab.grab.interface = NULL; + } +} + +static void +shell_map_popup(struct shell_surface *shsurf) +{ + struct shell_seat *shseat = shsurf->popup.shseat; + struct weston_surface *es = shsurf->surface; + struct weston_surface *parent = shsurf->parent; + + es->output = parent->output; + + weston_surface_set_transform_parent(es, parent); + weston_surface_set_position(es, shsurf->popup.x, shsurf->popup.y); + weston_surface_update_transform(es); + + if (shseat->seat->pointer->grab_serial == shsurf->popup.serial) { + add_popup_grab(shsurf, shseat); + } else { + wl_shell_surface_send_popup_done(shsurf->resource); + shseat->popup_grab.client = NULL; + } +} + +static void +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 shell_surface *shsurf = wl_resource_get_user_data(resource); + + shsurf->type = SHELL_SURFACE_POPUP; + shsurf->parent = wl_resource_get_user_data(parent_resource); + shsurf->popup.shseat = get_shell_seat(wl_resource_get_user_data(seat_resource)); + shsurf->popup.serial = serial; + shsurf->popup.x = x; + shsurf->popup.y = y; +} + +static const struct wl_shell_surface_interface shell_surface_implementation = { + shell_surface_pong, + shell_surface_move, + shell_surface_resize, + shell_surface_set_toplevel, + shell_surface_set_transient, + shell_surface_set_fullscreen, + shell_surface_set_popup, + shell_surface_set_maximized, + shell_surface_set_title, + shell_surface_set_class +}; + +static void +destroy_shell_surface(struct shell_surface *shsurf) +{ + wl_signal_emit(&shsurf->destroy_signal, shsurf); + + if (!wl_list_empty(&shsurf->popup.grab_link)) { + remove_popup_grab(shsurf); + } + + if (shsurf->fullscreen.type == WL_SHELL_SURFACE_FULLSCREEN_METHOD_DRIVER && + shell_surface_is_top_fullscreen(shsurf)) + restore_output_mode (shsurf->fullscreen_output); + + if (shsurf->fullscreen.black_surface) + weston_surface_destroy(shsurf->fullscreen.black_surface); + + /* As destroy_resource() use wl_list_for_each_safe(), + * we can always remove the listener. + */ + wl_list_remove(&shsurf->surface_destroy_listener.link); + shsurf->surface->configure = NULL; + ping_timer_destroy(shsurf); + free(shsurf->title); + + wl_list_remove(&shsurf->link); + free(shsurf); +} + +static void +shell_destroy_shell_surface(struct wl_resource *resource) +{ + struct shell_surface *shsurf = wl_resource_get_user_data(resource); + + destroy_shell_surface(shsurf); +} + +static void +shell_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct shell_surface *shsurf = container_of(listener, + struct shell_surface, + surface_destroy_listener); + + if (shsurf->resource) + wl_resource_destroy(shsurf->resource); + else + destroy_shell_surface(shsurf); +} + +static void +shell_surface_configure(struct weston_surface *, int32_t, int32_t, int32_t, int32_t); + +static struct shell_surface * +get_shell_surface(struct weston_surface *surface) +{ + if (surface->configure == shell_surface_configure) + return surface->configure_private; + else + return NULL; +} + +static struct shell_surface * +create_shell_surface(void *shell, struct weston_surface *surface, + const struct weston_shell_client *client) +{ + struct shell_surface *shsurf; + + if (surface->configure) { + weston_log("surface->configure already set\n"); + return NULL; + } + + shsurf = calloc(1, sizeof *shsurf); + if (!shsurf) { + weston_log("no memory to allocate shell surface\n"); + return NULL; + } + + surface->configure = shell_surface_configure; + surface->configure_private = shsurf; + + shsurf->shell = (struct desktop_shell *) shell; + shsurf->unresponsive = 0; + shsurf->saved_position_valid = false; + shsurf->saved_rotation_valid = false; + shsurf->surface = surface; + shsurf->fullscreen.type = WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT; + shsurf->fullscreen.framerate = 0; + shsurf->fullscreen.black_surface = NULL; + shsurf->ping_timer = NULL; + wl_list_init(&shsurf->fullscreen.transform.link); + + wl_signal_init(&shsurf->destroy_signal); + shsurf->surface_destroy_listener.notify = shell_handle_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &shsurf->surface_destroy_listener); + + /* init link so its safe to always remove it in destroy_shell_surface */ + wl_list_init(&shsurf->link); + wl_list_init(&shsurf->popup.grab_link); + + /* empty when not in use */ + wl_list_init(&shsurf->rotation.transform.link); + weston_matrix_init(&shsurf->rotation.rotation); + + wl_list_init(&shsurf->workspace_transform.link); + + shsurf->type = SHELL_SURFACE_NONE; + shsurf->next_type = SHELL_SURFACE_NONE; + + shsurf->client = client; + + return shsurf; +} + +static void +shell_get_shell_surface(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct desktop_shell *shell = wl_resource_get_user_data(resource); + struct shell_surface *shsurf; + + if (get_shell_surface(surface)) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "desktop_shell::get_shell_surface already requested"); + return; + } + + shsurf = create_shell_surface(shell, surface, &shell_client); + if (!shsurf) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface->configure already set"); + return; + } + + shsurf->resource = + wl_resource_create(client, + &wl_shell_surface_interface, 1, id); + wl_resource_set_implementation(shsurf->resource, + &shell_surface_implementation, + shsurf, shell_destroy_shell_surface); +} + +static const struct wl_shell_interface shell_implementation = { + shell_get_shell_surface +}; + +static void +shell_fade(struct desktop_shell *shell, enum fade_type type); + +static int +screensaver_timeout(void *data) +{ + struct desktop_shell *shell = data; + + shell_fade(shell, FADE_OUT); + + return 1; +} + +static void +handle_screensaver_sigchld(struct weston_process *proc, int status) +{ + struct desktop_shell *shell = + container_of(proc, struct desktop_shell, screensaver.process); + + proc->pid = 0; + + if (shell->locked) + weston_compositor_sleep(shell->compositor); +} + +static void +launch_screensaver(struct desktop_shell *shell) +{ + if (shell->screensaver.binding) + return; + + if (!shell->screensaver.path) { + weston_compositor_sleep(shell->compositor); + return; + } + + if (shell->screensaver.process.pid != 0) { + weston_log("old screensaver still running\n"); + return; + } + + weston_client_launch(shell->compositor, + &shell->screensaver.process, + shell->screensaver.path, + handle_screensaver_sigchld); +} + +static void +terminate_screensaver(struct desktop_shell *shell) +{ + if (shell->screensaver.process.pid == 0) + return; + + kill(shell->screensaver.process.pid, SIGTERM); +} + +static void +configure_static_surface(struct weston_surface *es, struct weston_layer *layer, int32_t width, int32_t height) +{ + struct weston_surface *s, *next; + + if (width == 0) + return; + + wl_list_for_each_safe(s, next, &layer->surface_list, layer_link) { + if (s->output == es->output && s != es) { + weston_surface_unmap(s); + s->configure = NULL; + } + } + + weston_surface_configure(es, es->output->x, es->output->y, width, height); + + if (wl_list_empty(&es->layer_link)) { + wl_list_insert(&layer->surface_list, &es->layer_link); + weston_compositor_schedule_repaint(es->compositor); + } +} + +static void +background_configure(struct weston_surface *es, int32_t sx, int32_t sy, int32_t width, int32_t height) +{ + struct desktop_shell *shell = es->configure_private; + + configure_static_surface(es, &shell->background_layer, width, height); +} + +static void +desktop_shell_set_background(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output_resource, + struct wl_resource *surface_resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + + if (surface->configure) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface role already assigned"); + return; + } + + surface->configure = background_configure; + surface->configure_private = shell; + surface->output = wl_resource_get_user_data(output_resource); + desktop_shell_send_configure(resource, 0, + surface_resource, + surface->output->width, + surface->output->height); +} + +static void +panel_configure(struct weston_surface *es, int32_t sx, int32_t sy, int32_t width, int32_t height) +{ + struct desktop_shell *shell = es->configure_private; + + configure_static_surface(es, &shell->panel_layer, width, height); +} + +static void +desktop_shell_set_panel(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output_resource, + struct wl_resource *surface_resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + + if (surface->configure) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface role already assigned"); + return; + } + + surface->configure = panel_configure; + surface->configure_private = shell; + surface->output = wl_resource_get_user_data(output_resource); + desktop_shell_send_configure(resource, 0, + surface_resource, + surface->output->width, + surface->output->height); +} + +static void +lock_surface_configure(struct weston_surface *surface, int32_t sx, int32_t sy, int32_t width, int32_t height) +{ + struct desktop_shell *shell = surface->configure_private; + + if (width == 0) + return; + + surface->geometry.width = width; + surface->geometry.height = height; + center_on_output(surface, get_default_output(shell->compositor)); + + if (!weston_surface_is_mapped(surface)) { + wl_list_insert(&shell->lock_layer.surface_list, + &surface->layer_link); + weston_surface_update_transform(surface); + shell_fade(shell, FADE_IN); + } +} + +static void +handle_lock_surface_destroy(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, lock_surface_listener); + + weston_log("lock surface gone\n"); + shell->lock_surface = NULL; +} + +static void +desktop_shell_set_lock_surface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + + shell->prepare_event_sent = false; + + if (!shell->locked) + return; + + shell->lock_surface = surface; + + shell->lock_surface_listener.notify = handle_lock_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &shell->lock_surface_listener); + + surface->configure = lock_surface_configure; + surface->configure_private = shell; +} + +static void +resume_desktop(struct desktop_shell *shell) +{ + struct workspace *ws = get_current_workspace(shell); + + terminate_screensaver(shell); + + wl_list_remove(&shell->lock_layer.link); + wl_list_insert(&shell->compositor->cursor_layer.link, + &shell->fullscreen_layer.link); + wl_list_insert(&shell->fullscreen_layer.link, + &shell->panel_layer.link); + if (shell->showing_input_panels) { + wl_list_insert(&shell->panel_layer.link, + &shell->input_panel_layer.link); + wl_list_insert(&shell->input_panel_layer.link, + &ws->layer.link); + } else { + wl_list_insert(&shell->panel_layer.link, &ws->layer.link); + } + + restore_focus_state(shell, get_current_workspace(shell)); + + shell->locked = false; + shell_fade(shell, FADE_IN); + weston_compositor_damage_all(shell->compositor); +} + +static void +desktop_shell_unlock(struct wl_client *client, + struct wl_resource *resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + + shell->prepare_event_sent = false; + + if (shell->locked) + resume_desktop(shell); +} + +static void +desktop_shell_set_grab_surface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + + shell->grab_surface = wl_resource_get_user_data(surface_resource); +} + +static void +desktop_shell_desktop_ready(struct wl_client *client, + struct wl_resource *resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + + shell_fade_startup(shell); +} + +static const struct desktop_shell_interface desktop_shell_implementation = { + desktop_shell_set_background, + desktop_shell_set_panel, + desktop_shell_set_lock_surface, + desktop_shell_unlock, + desktop_shell_set_grab_surface, + desktop_shell_desktop_ready +}; + +static enum shell_surface_type +get_shell_surface_type(struct weston_surface *surface) +{ + struct shell_surface *shsurf; + + shsurf = get_shell_surface(surface); + if (!shsurf) + return SHELL_SURFACE_NONE; + return shsurf->type; +} + +static void +move_binding(struct weston_seat *seat, uint32_t time, uint32_t button, void *data) +{ + struct weston_surface *focus = + (struct weston_surface *) seat->pointer->focus; + struct weston_surface *surface; + struct shell_surface *shsurf; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL || shsurf->type == SHELL_SURFACE_FULLSCREEN || + shsurf->type == SHELL_SURFACE_MAXIMIZED) + return; + + surface_move(shsurf, (struct weston_seat *) seat); +} + +static void +touch_move_binding(struct weston_seat *seat, uint32_t time, void *data) +{ + struct weston_surface *focus = + (struct weston_surface *) seat->touch->focus; + struct weston_surface *surface; + struct shell_surface *shsurf; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (shsurf == NULL || shsurf->type == SHELL_SURFACE_FULLSCREEN || + shsurf->type == SHELL_SURFACE_MAXIMIZED) + return; + + surface_touch_move(shsurf, (struct weston_seat *) seat); +} + +static void +resize_binding(struct weston_seat *seat, uint32_t time, uint32_t button, void *data) +{ + struct weston_surface *focus = + (struct weston_surface *) seat->pointer->focus; + struct weston_surface *surface; + uint32_t edges = 0; + int32_t x, y; + struct shell_surface *shsurf; + + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (!shsurf || shsurf->type == SHELL_SURFACE_FULLSCREEN || + shsurf->type == SHELL_SURFACE_MAXIMIZED) + return; + + weston_surface_from_global(surface, + wl_fixed_to_int(seat->pointer->grab_x), + wl_fixed_to_int(seat->pointer->grab_y), + &x, &y); + + if (x < surface->geometry.width / 3) + edges |= WL_SHELL_SURFACE_RESIZE_LEFT; + else if (x < 2 * surface->geometry.width / 3) + edges |= 0; + else + edges |= WL_SHELL_SURFACE_RESIZE_RIGHT; + + if (y < surface->geometry.height / 3) + edges |= WL_SHELL_SURFACE_RESIZE_TOP; + else if (y < 2 * surface->geometry.height / 3) + edges |= 0; + else + edges |= WL_SHELL_SURFACE_RESIZE_BOTTOM; + + surface_resize(shsurf, (struct weston_seat *) seat, edges); +} + +static void +surface_opacity_binding(struct weston_seat *seat, uint32_t time, uint32_t axis, + wl_fixed_t value, void *data) +{ + float step = 0.005; + struct shell_surface *shsurf; + struct weston_surface *focus = + (struct weston_surface *) seat->pointer->focus; + struct weston_surface *surface; + + /* XXX: broken for windows containing sub-surfaces */ + surface = weston_surface_get_main_surface(focus); + if (surface == NULL) + return; + + shsurf = get_shell_surface(surface); + if (!shsurf) + return; + + surface->alpha -= wl_fixed_to_double(value) * step; + + if (surface->alpha > 1.0) + surface->alpha = 1.0; + if (surface->alpha < step) + surface->alpha = step; + + weston_surface_geometry_dirty(surface); + weston_surface_damage(surface); +} + +static void +do_zoom(struct weston_seat *seat, uint32_t time, uint32_t key, uint32_t axis, + wl_fixed_t value) +{ + struct weston_seat *ws = (struct weston_seat *) seat; + struct weston_compositor *compositor = ws->compositor; + struct weston_output *output; + float increment; + + wl_list_for_each(output, &compositor->output_list, link) { + if (pixman_region32_contains_point(&output->region, + wl_fixed_to_double(seat->pointer->x), + wl_fixed_to_double(seat->pointer->y), + NULL)) { + if (key == KEY_PAGEUP) + increment = output->zoom.increment; + else if (key == KEY_PAGEDOWN) + increment = -output->zoom.increment; + else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) + /* For every pixel zoom 20th of a step */ + increment = output->zoom.increment * + -wl_fixed_to_double(value) / 20.0; + else + increment = 0; + + output->zoom.level += increment; + + if (output->zoom.level < 0.0) + output->zoom.level = 0.0; + else if (output->zoom.level > output->zoom.max_level) + output->zoom.level = output->zoom.max_level; + else if (!output->zoom.active) { + output->zoom.active = 1; + output->disable_planes++; + } + + output->zoom.spring_z.target = output->zoom.level; + + weston_output_update_zoom(output, output->zoom.type); + } + } +} + +static void +zoom_axis_binding(struct weston_seat *seat, uint32_t time, uint32_t axis, + wl_fixed_t value, void *data) +{ + do_zoom(seat, time, 0, axis, value); +} + +static void +zoom_key_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + do_zoom(seat, time, key, 0, 0); +} + +static void +terminate_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + struct weston_compositor *compositor = data; + + wl_display_terminate(compositor->wl_display); +} + +static void +rotate_grab_motion(struct weston_pointer_grab *grab, uint32_t time) +{ + struct rotate_grab *rotate = + container_of(grab, struct rotate_grab, base.grab); + struct weston_pointer *pointer = grab->pointer; + struct shell_surface *shsurf = rotate->base.shsurf; + struct weston_surface *surface; + float cx, cy, dx, dy, cposx, cposy, dposx, dposy, r; + + if (!shsurf) + return; + + surface = shsurf->surface; + + cx = 0.5f * surface->geometry.width; + cy = 0.5f * surface->geometry.height; + + dx = wl_fixed_to_double(pointer->x) - rotate->center.x; + dy = wl_fixed_to_double(pointer->y) - rotate->center.y; + r = sqrtf(dx * dx + dy * dy); + + wl_list_remove(&shsurf->rotation.transform.link); + weston_surface_geometry_dirty(shsurf->surface); + + if (r > 20.0f) { + struct weston_matrix *matrix = + &shsurf->rotation.transform.matrix; + + weston_matrix_init(&rotate->rotation); + weston_matrix_rotate_xy(&rotate->rotation, dx / r, dy / r); + + weston_matrix_init(matrix); + weston_matrix_translate(matrix, -cx, -cy, 0.0f); + weston_matrix_multiply(matrix, &shsurf->rotation.rotation); + weston_matrix_multiply(matrix, &rotate->rotation); + weston_matrix_translate(matrix, cx, cy, 0.0f); + + wl_list_insert( + &shsurf->surface->geometry.transformation_list, + &shsurf->rotation.transform.link); + } else { + wl_list_init(&shsurf->rotation.transform.link); + weston_matrix_init(&shsurf->rotation.rotation); + weston_matrix_init(&rotate->rotation); + } + + /* We need to adjust the position of the surface + * in case it was resized in a rotated state before */ + cposx = surface->geometry.x + cx; + cposy = surface->geometry.y + cy; + dposx = rotate->center.x - cposx; + dposy = rotate->center.y - cposy; + if (dposx != 0.0f || dposy != 0.0f) { + weston_surface_set_position(surface, + surface->geometry.x + dposx, + surface->geometry.y + dposy); + } + + /* Repaint implies weston_surface_update_transform(), which + * lazily applies the damage due to rotation update. + */ + weston_compositor_schedule_repaint(shsurf->surface->compositor); +} + +static void +rotate_grab_button(struct weston_pointer_grab *grab, + uint32_t time, uint32_t button, uint32_t state_w) +{ + struct rotate_grab *rotate = + container_of(grab, struct rotate_grab, base.grab); + struct weston_pointer *pointer = grab->pointer; + struct shell_surface *shsurf = rotate->base.shsurf; + enum wl_pointer_button_state state = state_w; + + if (pointer->button_count == 0 && + state == WL_POINTER_BUTTON_STATE_RELEASED) { + if (shsurf) + weston_matrix_multiply(&shsurf->rotation.rotation, + &rotate->rotation); + shell_grab_end(&rotate->base); + free(rotate); + } +} + +static void +rotate_grab_cancel(struct weston_pointer_grab *grab) +{ + struct rotate_grab *rotate = + container_of(grab, struct rotate_grab, base.grab); + + shell_grab_end(&rotate->base); + free(rotate); +} + +static const struct weston_pointer_grab_interface rotate_grab_interface = { + noop_grab_focus, + rotate_grab_motion, + rotate_grab_button, + rotate_grab_cancel, +}; + +static void +surface_rotate(struct shell_surface *surface, struct weston_seat *seat) +{ + struct rotate_grab *rotate; + float dx, dy; + float r; + + rotate = malloc(sizeof *rotate); + if (!rotate) + return; + + weston_surface_to_global_float(surface->surface, + surface->surface->geometry.width * 0.5f, + surface->surface->geometry.height * 0.5f, + &rotate->center.x, &rotate->center.y); + + dx = wl_fixed_to_double(seat->pointer->x) - rotate->center.x; + dy = wl_fixed_to_double(seat->pointer->y) - rotate->center.y; + r = sqrtf(dx * dx + dy * dy); + if (r > 20.0f) { + struct weston_matrix inverse; + + weston_matrix_init(&inverse); + weston_matrix_rotate_xy(&inverse, dx / r, -dy / r); + weston_matrix_multiply(&surface->rotation.rotation, &inverse); + + weston_matrix_init(&rotate->rotation); + weston_matrix_rotate_xy(&rotate->rotation, dx / r, dy / r); + } else { + weston_matrix_init(&surface->rotation.rotation); + weston_matrix_init(&rotate->rotation); + } + + shell_grab_start(&rotate->base, &rotate_grab_interface, surface, + seat->pointer, DESKTOP_SHELL_CURSOR_ARROW); +} + +static void +rotate_binding(struct weston_seat *seat, uint32_t time, uint32_t button, + void *data) +{ + struct weston_surface *focus = + (struct weston_surface *) seat->pointer->focus; + struct weston_surface *base_surface; + struct shell_surface *surface; + + base_surface = weston_surface_get_main_surface(focus); + if (base_surface == NULL) + return; + + surface = get_shell_surface(base_surface); + if (!surface || surface->type == SHELL_SURFACE_FULLSCREEN || + surface->type == SHELL_SURFACE_MAXIMIZED) + return; + + surface_rotate(surface, seat); +} + +static void +lower_fullscreen_layer(struct desktop_shell *shell) +{ + struct workspace *ws; + struct weston_surface *surface, *prev; + + ws = get_current_workspace(shell); + wl_list_for_each_reverse_safe(surface, prev, + &shell->fullscreen_layer.surface_list, + layer_link) + weston_surface_restack(surface, &ws->layer.surface_list); +} + +static void +activate(struct desktop_shell *shell, struct weston_surface *es, + struct weston_seat *seat) +{ + struct weston_surface *main_surface; + struct focus_state *state; + struct workspace *ws; + + main_surface = weston_surface_get_main_surface(es); + + weston_surface_activate(es, seat); + + state = ensure_focus_state(shell, seat); + if (state == NULL) + return; + + state->keyboard_focus = es; + wl_list_remove(&state->surface_destroy_listener.link); + wl_signal_add(&es->destroy_signal, &state->surface_destroy_listener); + + switch (get_shell_surface_type(main_surface)) { + case SHELL_SURFACE_FULLSCREEN: + /* should on top of panels */ + shell_stack_fullscreen(get_shell_surface(main_surface)); + shell_configure_fullscreen(get_shell_surface(main_surface)); + break; + default: + restore_all_output_modes(shell->compositor); + ws = get_current_workspace(shell); + weston_surface_restack(main_surface, &ws->layer.surface_list); + break; + } +} + +/* no-op func for checking black surface */ +static void +black_surface_configure(struct weston_surface *es, int32_t sx, int32_t sy, int32_t width, int32_t height) +{ +} + +static bool +is_black_surface (struct weston_surface *es, struct weston_surface **fs_surface) +{ + if (es->configure == black_surface_configure) { + if (fs_surface) + *fs_surface = (struct weston_surface *)es->configure_private; + return true; + } + return false; +} + +static void +activate_binding(struct weston_seat *seat, + struct desktop_shell *shell, + struct weston_surface *focus) +{ + struct weston_surface *main_surface; + + if (!focus) + return; + + if (is_black_surface(focus, &main_surface)) + focus = main_surface; + + main_surface = weston_surface_get_main_surface(focus); + if (get_shell_surface_type(main_surface) == SHELL_SURFACE_NONE) + return; + + activate(shell, focus, seat); +} + +static void +click_to_activate_binding(struct weston_seat *seat, uint32_t time, uint32_t button, + void *data) +{ + if (seat->pointer->grab != &seat->pointer->default_grab) + return; + + activate_binding(seat, data, + (struct weston_surface *) seat->pointer->focus); +} + +static void +touch_to_activate_binding(struct weston_seat *seat, uint32_t time, void *data) +{ + if (seat->touch->grab != &seat->touch->default_grab) + return; + + activate_binding(seat, data, + (struct weston_surface *) seat->touch->focus); +} + +static void +lock(struct desktop_shell *shell) +{ + struct workspace *ws = get_current_workspace(shell); + + if (shell->locked) { + weston_compositor_sleep(shell->compositor); + return; + } + + shell->locked = true; + + /* Hide all surfaces by removing the fullscreen, panel and + * toplevel layers. This way nothing else can show or receive + * input events while we are locked. */ + + wl_list_remove(&shell->panel_layer.link); + wl_list_remove(&shell->fullscreen_layer.link); + if (shell->showing_input_panels) + wl_list_remove(&shell->input_panel_layer.link); + wl_list_remove(&ws->layer.link); + wl_list_insert(&shell->compositor->cursor_layer.link, + &shell->lock_layer.link); + + launch_screensaver(shell); + + /* TODO: disable bindings that should not work while locked. */ + + /* All this must be undone in resume_desktop(). */ +} + +static void +unlock(struct desktop_shell *shell) +{ + if (!shell->locked || shell->lock_surface) { + shell_fade(shell, FADE_IN); + return; + } + + /* If desktop-shell client has gone away, unlock immediately. */ + if (!shell->child.desktop_shell) { + resume_desktop(shell); + return; + } + + if (shell->prepare_event_sent) + return; + + desktop_shell_send_prepare_lock_surface(shell->child.desktop_shell); + shell->prepare_event_sent = true; +} + +static void +shell_fade_done(struct weston_surface_animation *animation, void *data) +{ + struct desktop_shell *shell = data; + + shell->fade.animation = NULL; + + switch (shell->fade.type) { + case FADE_IN: + weston_surface_destroy(shell->fade.surface); + shell->fade.surface = NULL; + break; + case FADE_OUT: + lock(shell); + break; + } +} + +static struct weston_surface * +shell_fade_create_surface(struct desktop_shell *shell) +{ + struct weston_compositor *compositor = shell->compositor; + struct weston_surface *surface; + + surface = weston_surface_create(compositor); + if (!surface) + return NULL; + + weston_surface_configure(surface, 0, 0, 8192, 8192); + weston_surface_set_color(surface, 0.0, 0.0, 0.0, 1.0); + wl_list_insert(&compositor->fade_layer.surface_list, + &surface->layer_link); + pixman_region32_init(&surface->input); + + return surface; +} + +static void +shell_fade(struct desktop_shell *shell, enum fade_type type) +{ + float tint; + + switch (type) { + case FADE_IN: + tint = 0.0; + break; + case FADE_OUT: + tint = 1.0; + break; + default: + weston_log("shell: invalid fade type\n"); + return; + } + + shell->fade.type = type; + + if (shell->fade.surface == NULL) { + shell->fade.surface = shell_fade_create_surface(shell); + if (!shell->fade.surface) + return; + + shell->fade.surface->alpha = 1.0 - tint; + weston_surface_update_transform(shell->fade.surface); + } + + if (shell->fade.animation) + weston_fade_update(shell->fade.animation, tint); + else + shell->fade.animation = + weston_fade_run(shell->fade.surface, + 1.0 - tint, tint, 300.0, + shell_fade_done, shell); +} + +static void +do_shell_fade_startup(void *data) +{ + struct desktop_shell *shell = data; + + if (shell->startup_animation_type == ANIMATION_FADE) + shell_fade(shell, FADE_IN); + else if (shell->startup_animation_type == ANIMATION_NONE) { + weston_surface_destroy(shell->fade.surface); + shell->fade.surface = NULL; + } +} + +static void +shell_fade_startup(struct desktop_shell *shell) +{ + struct wl_event_loop *loop; + + if (!shell->fade.startup_timer) + return; + + wl_event_source_remove(shell->fade.startup_timer); + shell->fade.startup_timer = NULL; + + loop = wl_display_get_event_loop(shell->compositor->wl_display); + wl_event_loop_add_idle(loop, do_shell_fade_startup, shell); +} + +static int +fade_startup_timeout(void *data) +{ + struct desktop_shell *shell = data; + + shell_fade_startup(shell); + return 0; +} + +static void +shell_fade_init(struct desktop_shell *shell) +{ + /* Make compositor output all black, and wait for the desktop-shell + * client to signal it is ready, then fade in. The timer triggers a + * fade-in, in case the desktop-shell client takes too long. + */ + + struct wl_event_loop *loop; + + if (shell->fade.surface != NULL) { + weston_log("%s: warning: fade surface already exists\n", + __func__); + return; + } + + shell->fade.surface = shell_fade_create_surface(shell); + if (!shell->fade.surface) + return; + + weston_surface_update_transform(shell->fade.surface); + weston_surface_damage(shell->fade.surface); + + loop = wl_display_get_event_loop(shell->compositor->wl_display); + shell->fade.startup_timer = + wl_event_loop_add_timer(loop, fade_startup_timeout, shell); + wl_event_source_timer_update(shell->fade.startup_timer, 15000); +} + +static void +idle_handler(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, idle_listener); + + shell_fade(shell, FADE_OUT); + /* lock() is called from shell_fade_done() */ +} + +static void +wake_handler(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, wake_listener); + + unlock(shell); +} + +static void +show_input_panels(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, + show_input_panel_listener); + struct input_panel_surface *surface, *next; + struct weston_surface *ws; + + shell->text_input.surface = (struct weston_surface*)data; + + if (shell->showing_input_panels) + return; + + shell->showing_input_panels = true; + + if (!shell->locked) + wl_list_insert(&shell->panel_layer.link, + &shell->input_panel_layer.link); + + wl_list_for_each_safe(surface, next, + &shell->input_panel.surfaces, link) { + ws = surface->surface; + if (!ws->buffer_ref.buffer) + continue; + wl_list_insert(&shell->input_panel_layer.surface_list, + &ws->layer_link); + weston_surface_geometry_dirty(ws); + weston_surface_update_transform(ws); + weston_surface_damage(ws); + weston_slide_run(ws, ws->geometry.height, 0, NULL, NULL); + } +} + +static void +hide_input_panels(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, + hide_input_panel_listener); + struct weston_surface *surface, *next; + + if (!shell->showing_input_panels) + return; + + shell->showing_input_panels = false; + + if (!shell->locked) + wl_list_remove(&shell->input_panel_layer.link); + + wl_list_for_each_safe(surface, next, + &shell->input_panel_layer.surface_list, layer_link) + weston_surface_unmap(surface); +} + +static void +update_input_panels(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, + update_input_panel_listener); + + memcpy(&shell->text_input.cursor_rectangle, data, sizeof(pixman_box32_t)); +} + +static void +center_on_output(struct weston_surface *surface, struct weston_output *output) +{ + int32_t surf_x, surf_y, width, height; + float x, y; + + surface_subsurfaces_boundingbox(surface, &surf_x, &surf_y, &width, &height); + + x = output->x + (output->width - width) / 2 - surf_x / 2; + y = output->y + (output->height - height) / 2 - surf_y / 2; + + weston_surface_configure(surface, x, y, width, height); +} + +static void +weston_surface_set_initial_position (struct weston_surface *surface, + struct desktop_shell *shell) +{ + struct weston_compositor *compositor = shell->compositor; + int ix = 0, iy = 0; + int range_x, range_y; + int dx, dy, x, y, panel_height; + struct weston_output *output, *target_output = NULL; + struct weston_seat *seat; + + /* As a heuristic place the new window on the same output as the + * pointer. Falling back to the output containing 0, 0. + * + * TODO: Do something clever for touch too? + */ + wl_list_for_each(seat, &compositor->seat_list, link) { + if (seat->pointer) { + ix = wl_fixed_to_int(seat->pointer->x); + iy = wl_fixed_to_int(seat->pointer->y); + break; + } + } + + wl_list_for_each(output, &compositor->output_list, link) { + if (pixman_region32_contains_point(&output->region, ix, iy, NULL)) { + target_output = output; + break; + } + } + + if (!target_output) { + weston_surface_set_position(surface, 10 + random() % 400, + 10 + random() % 400); + return; + } + + /* Valid range within output where the surface will still be onscreen. + * If this is negative it means that the surface is bigger than + * output. + */ + panel_height = get_output_panel_height(shell, target_output); + range_x = target_output->width - surface->geometry.width; + range_y = (target_output->height - panel_height) - + surface->geometry.height; + + if (range_x > 0) + dx = random() % range_x; + else + dx = 0; + + if (range_y > 0) + dy = panel_height + random() % range_y; + else + dy = panel_height; + + x = target_output->x + dx; + y = target_output->y + dy; + + weston_surface_set_position (surface, x, y); +} + +static void +map(struct desktop_shell *shell, struct weston_surface *surface, + int32_t width, int32_t height, int32_t sx, int32_t sy) +{ + struct weston_compositor *compositor = shell->compositor; + struct shell_surface *shsurf = get_shell_surface(surface); + enum shell_surface_type surface_type = shsurf->type; + struct weston_surface *parent; + struct weston_seat *seat; + struct workspace *ws; + int panel_height = 0; + int32_t surf_x, surf_y; + + surface->geometry.width = width; + surface->geometry.height = height; + weston_surface_geometry_dirty(surface); + + /* initial positioning, see also configure() */ + switch (surface_type) { + case SHELL_SURFACE_TOPLEVEL: + weston_surface_set_initial_position(surface, shell); + break; + case SHELL_SURFACE_FULLSCREEN: + center_on_output(surface, shsurf->fullscreen_output); + shell_map_fullscreen(shsurf); + break; + case SHELL_SURFACE_MAXIMIZED: + /* use surface configure to set the geometry */ + panel_height = get_output_panel_height(shell,surface->output); + surface_subsurfaces_boundingbox(shsurf->surface, &surf_x, &surf_y, + NULL, NULL); + weston_surface_set_position(surface, shsurf->output->x - surf_x, + shsurf->output->y + panel_height - surf_y); + break; + case SHELL_SURFACE_POPUP: + shell_map_popup(shsurf); + break; + case SHELL_SURFACE_NONE: + weston_surface_set_position(surface, + surface->geometry.x + sx, + surface->geometry.y + sy); + break; + default: + ; + } + + /* surface stacking order, see also activate() */ + switch (surface_type) { + case SHELL_SURFACE_POPUP: + case SHELL_SURFACE_TRANSIENT: + parent = shsurf->parent; + wl_list_insert(parent->layer_link.prev, &surface->layer_link); + break; + case SHELL_SURFACE_FULLSCREEN: + case SHELL_SURFACE_NONE: + break; + case SHELL_SURFACE_XWAYLAND: + default: + ws = get_current_workspace(shell); + wl_list_insert(&ws->layer.surface_list, &surface->layer_link); + break; + } + + if (surface_type != SHELL_SURFACE_NONE) { + weston_surface_update_transform(surface); + if (surface_type == SHELL_SURFACE_MAXIMIZED) + surface->output = shsurf->output; + } + + switch (surface_type) { + /* XXX: xwayland's using the same fields for transient type */ + case SHELL_SURFACE_XWAYLAND: + case SHELL_SURFACE_TRANSIENT: + if (shsurf->transient.flags == + WL_SHELL_SURFACE_TRANSIENT_INACTIVE) + break; + case SHELL_SURFACE_TOPLEVEL: + case SHELL_SURFACE_FULLSCREEN: + case SHELL_SURFACE_MAXIMIZED: + if (!shell->locked) { + wl_list_for_each(seat, &compositor->seat_list, link) + activate(shell, surface, seat); + } + break; + default: + break; + } + + if (surface_type == SHELL_SURFACE_TOPLEVEL) + { + switch (shell->win_animation_type) { + case ANIMATION_FADE: + weston_fade_run(surface, 0.0, 1.0, 300.0, NULL, NULL); + break; + case ANIMATION_ZOOM: + weston_zoom_run(surface, 0.5, 1.0, NULL, NULL); + break; + default: + break; + } + } +} + +static void +configure(struct desktop_shell *shell, struct weston_surface *surface, + float x, float y, int32_t width, int32_t height) +{ + enum shell_surface_type surface_type = SHELL_SURFACE_NONE; + struct shell_surface *shsurf; + int32_t surf_x, surf_y; + + shsurf = get_shell_surface(surface); + if (shsurf) + surface_type = shsurf->type; + + weston_surface_configure(surface, x, y, width, height); + + switch (surface_type) { + case SHELL_SURFACE_FULLSCREEN: + shell_stack_fullscreen(shsurf); + shell_configure_fullscreen(shsurf); + break; + case SHELL_SURFACE_MAXIMIZED: + /* setting x, y and using configure to change that geometry */ + surface_subsurfaces_boundingbox(shsurf->surface, &surf_x, &surf_y, + NULL, NULL); + surface->geometry.x = surface->output->x - surf_x; + surface->geometry.y = surface->output->y + + get_output_panel_height(shell,surface->output) - surf_y; + break; + case SHELL_SURFACE_TOPLEVEL: + break; + default: + break; + } + + /* XXX: would a fullscreen surface need the same handling? */ + if (surface->output) { + weston_surface_update_transform(surface); + + if (surface_type == SHELL_SURFACE_MAXIMIZED) + surface->output = shsurf->output; + } +} + +static void +shell_surface_configure(struct weston_surface *es, int32_t sx, int32_t sy, int32_t width, int32_t height) +{ + struct shell_surface *shsurf = get_shell_surface(es); + struct desktop_shell *shell = shsurf->shell; + + int type_changed = 0; + + if (!weston_surface_is_mapped(es) && + !wl_list_empty(&shsurf->popup.grab_link)) { + remove_popup_grab(shsurf); + } + + if (width == 0) + return; + + if (shsurf->next_type != SHELL_SURFACE_NONE && + shsurf->type != shsurf->next_type) { + set_surface_type(shsurf); + type_changed = 1; + } + + if (!weston_surface_is_mapped(es)) { + map(shell, es, width, height, sx, sy); + } else if (type_changed || sx != 0 || sy != 0 || + es->geometry.width != width || + es->geometry.height != height) { + float from_x, from_y; + float to_x, to_y; + + weston_surface_to_global_float(es, 0, 0, &from_x, &from_y); + weston_surface_to_global_float(es, sx, sy, &to_x, &to_y); + configure(shell, es, + es->geometry.x + to_x - from_x, + es->geometry.y + to_y - from_y, + width, height); + } +} + +static void launch_desktop_shell_process(void *data); + +static void +desktop_shell_sigchld(struct weston_process *process, int status) +{ + uint32_t time; + struct desktop_shell *shell = + container_of(process, struct desktop_shell, child.process); + + shell->child.process.pid = 0; + shell->child.client = NULL; /* already destroyed by wayland */ + + /* if desktop-shell dies more than 5 times in 30 seconds, give up */ + time = weston_compositor_get_time(); + if (time - shell->child.deathstamp > 30000) { + shell->child.deathstamp = time; + shell->child.deathcount = 0; + } + + shell->child.deathcount++; + if (shell->child.deathcount > 5) { + weston_log("weston-desktop-shell died, giving up.\n"); + return; + } + + weston_log("weston-desktop-shell died, respawning...\n"); + launch_desktop_shell_process(shell); + shell_fade_startup(shell); +} + +static void +launch_desktop_shell_process(void *data) +{ + struct desktop_shell *shell = data; + const char *shell_exe = LIBEXECDIR "/weston-desktop-shell"; + + shell->child.client = weston_client_launch(shell->compositor, + &shell->child.process, + shell_exe, + desktop_shell_sigchld); + + if (!shell->child.client) + weston_log("not able to start %s\n", shell_exe); +} + +static void +bind_shell(struct wl_client *client, void *data, uint32_t version, uint32_t id) +{ + struct desktop_shell *shell = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &wl_shell_interface, 1, id); + if (resource) + wl_resource_set_implementation(resource, &shell_implementation, + shell, NULL); +} + +static void +unbind_desktop_shell(struct wl_resource *resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + + if (shell->locked) + resume_desktop(shell); + + shell->child.desktop_shell = NULL; + shell->prepare_event_sent = false; +} + +static void +bind_desktop_shell(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct desktop_shell *shell = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &desktop_shell_interface, + MIN(version, 2), id); + + if (client == shell->child.client) { + wl_resource_set_implementation(resource, + &desktop_shell_implementation, + shell, unbind_desktop_shell); + shell->child.desktop_shell = resource; + + if (version < 2) + shell_fade_startup(shell); + + return; + } + + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "permission to bind desktop_shell denied"); + wl_resource_destroy(resource); +} + +static void +screensaver_configure(struct weston_surface *surface, int32_t sx, int32_t sy, int32_t width, int32_t height) +{ + struct desktop_shell *shell = surface->configure_private; + + if (width == 0) + return; + + /* XXX: starting weston-screensaver beforehand does not work */ + if (!shell->locked) + return; + + center_on_output(surface, surface->output); + + if (wl_list_empty(&surface->layer_link)) { + wl_list_insert(shell->lock_layer.surface_list.prev, + &surface->layer_link); + weston_surface_update_transform(surface); + wl_event_source_timer_update(shell->screensaver.timer, + shell->screensaver.duration); + shell_fade(shell, FADE_IN); + } +} + +static void +screensaver_set_surface(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource, + struct wl_resource *output_resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct weston_output *output = wl_resource_get_user_data(output_resource); + + surface->configure = screensaver_configure; + surface->configure_private = shell; + surface->output = output; +} + +static const struct screensaver_interface screensaver_implementation = { + screensaver_set_surface +}; + +static void +unbind_screensaver(struct wl_resource *resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + + shell->screensaver.binding = NULL; +} + +static void +bind_screensaver(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct desktop_shell *shell = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &screensaver_interface, 1, id); + + if (shell->screensaver.binding == NULL) { + wl_resource_set_implementation(resource, + &screensaver_implementation, + shell, unbind_screensaver); + shell->screensaver.binding = resource; + return; + } + + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "interface object already bound"); + wl_resource_destroy(resource); +} + +static void +input_panel_configure(struct weston_surface *surface, int32_t sx, int32_t sy, int32_t width, int32_t height) +{ + struct input_panel_surface *ip_surface = surface->configure_private; + struct desktop_shell *shell = ip_surface->shell; + float x, y; + uint32_t show_surface = 0; + + if (width == 0) + return; + + if (!weston_surface_is_mapped(surface)) { + if (!shell->showing_input_panels) + return; + + show_surface = 1; + } + + fprintf(stderr, "%s panel: %d, output: %p\n", __FUNCTION__, ip_surface->panel, ip_surface->output); + + if (ip_surface->panel) { + x = shell->text_input.surface->geometry.x + shell->text_input.cursor_rectangle.x2; + y = shell->text_input.surface->geometry.y + shell->text_input.cursor_rectangle.y2; + } else { + x = ip_surface->output->x + (ip_surface->output->width - width) / 2; + y = ip_surface->output->y + ip_surface->output->height - height; + } + + weston_surface_configure(surface, + x, y, + width, height); + + if (show_surface) { + wl_list_insert(&shell->input_panel_layer.surface_list, + &surface->layer_link); + weston_surface_update_transform(surface); + weston_surface_damage(surface); + weston_slide_run(surface, surface->geometry.height, 0, NULL, NULL); + } +} + +static void +destroy_input_panel_surface(struct input_panel_surface *input_panel_surface) +{ + wl_signal_emit(&input_panel_surface->destroy_signal, input_panel_surface); + + wl_list_remove(&input_panel_surface->surface_destroy_listener.link); + wl_list_remove(&input_panel_surface->link); + + input_panel_surface->surface->configure = NULL; + + free(input_panel_surface); +} + +static struct input_panel_surface * +get_input_panel_surface(struct weston_surface *surface) +{ + if (surface->configure == input_panel_configure) { + return surface->configure_private; + } else { + return NULL; + } +} + +static void +input_panel_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct input_panel_surface *ipsurface = container_of(listener, + struct input_panel_surface, + surface_destroy_listener); + + if (ipsurface->resource) { + wl_resource_destroy(ipsurface->resource); + } else { + destroy_input_panel_surface(ipsurface); + } +} + +static struct input_panel_surface * +create_input_panel_surface(struct desktop_shell *shell, + struct weston_surface *surface) +{ + struct input_panel_surface *input_panel_surface; + + input_panel_surface = calloc(1, sizeof *input_panel_surface); + if (!input_panel_surface) + return NULL; + + surface->configure = input_panel_configure; + surface->configure_private = input_panel_surface; + + input_panel_surface->shell = shell; + + input_panel_surface->surface = surface; + + wl_signal_init(&input_panel_surface->destroy_signal); + input_panel_surface->surface_destroy_listener.notify = input_panel_handle_surface_destroy; + wl_signal_add(&surface->destroy_signal, + &input_panel_surface->surface_destroy_listener); + + wl_list_init(&input_panel_surface->link); + + return input_panel_surface; +} + +static void +input_panel_surface_set_toplevel(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output_resource, + uint32_t position) +{ + struct input_panel_surface *input_panel_surface = + wl_resource_get_user_data(resource); + struct desktop_shell *shell = input_panel_surface->shell; + + wl_list_insert(&shell->input_panel.surfaces, + &input_panel_surface->link); + + input_panel_surface->output = wl_resource_get_user_data(output_resource); + input_panel_surface->panel = 0; +} + +static void +input_panel_surface_set_overlay_panel(struct wl_client *client, + struct wl_resource *resource) +{ + struct input_panel_surface *input_panel_surface = + wl_resource_get_user_data(resource); + struct desktop_shell *shell = input_panel_surface->shell; + + wl_list_insert(&shell->input_panel.surfaces, + &input_panel_surface->link); + + input_panel_surface->panel = 1; +} + +static const struct wl_input_panel_surface_interface input_panel_surface_implementation = { + input_panel_surface_set_toplevel, + input_panel_surface_set_overlay_panel +}; + +static void +destroy_input_panel_surface_resource(struct wl_resource *resource) +{ + struct input_panel_surface *ipsurf = + wl_resource_get_user_data(resource); + + destroy_input_panel_surface(ipsurf); +} + +static void +input_panel_get_input_panel_surface(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface_resource) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct desktop_shell *shell = wl_resource_get_user_data(resource); + struct input_panel_surface *ipsurf; + + if (get_input_panel_surface(surface)) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "wl_input_panel::get_input_panel_surface already requested"); + return; + } + + ipsurf = create_input_panel_surface(shell, surface); + if (!ipsurf) { + wl_resource_post_error(surface_resource, + WL_DISPLAY_ERROR_INVALID_OBJECT, + "surface->configure already set"); + return; + } + + ipsurf->resource = + wl_resource_create(client, + &wl_input_panel_surface_interface, 1, id); + wl_resource_set_implementation(ipsurf->resource, + &input_panel_surface_implementation, + ipsurf, + destroy_input_panel_surface_resource); +} + +static const struct wl_input_panel_interface input_panel_implementation = { + input_panel_get_input_panel_surface +}; + +static void +unbind_input_panel(struct wl_resource *resource) +{ + struct desktop_shell *shell = wl_resource_get_user_data(resource); + + shell->input_panel.binding = NULL; +} + +static void +bind_input_panel(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct desktop_shell *shell = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, + &wl_input_panel_interface, 1, id); + + if (shell->input_panel.binding == NULL) { + wl_resource_set_implementation(resource, + &input_panel_implementation, + shell, unbind_input_panel); + shell->input_panel.binding = resource; + return; + } + + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "interface object already bound"); + wl_resource_destroy(resource); +} + +struct switcher { + struct desktop_shell *shell; + struct weston_surface *current; + struct wl_listener listener; + struct weston_keyboard_grab grab; +}; + +static void +switcher_next(struct switcher *switcher) +{ + struct weston_surface *surface; + struct weston_surface *first = NULL, *prev = NULL, *next = NULL; + struct shell_surface *shsurf; + struct workspace *ws = get_current_workspace(switcher->shell); + + wl_list_for_each(surface, &ws->layer.surface_list, layer_link) { + switch (get_shell_surface_type(surface)) { + case SHELL_SURFACE_TOPLEVEL: + case SHELL_SURFACE_FULLSCREEN: + case SHELL_SURFACE_MAXIMIZED: + if (first == NULL) + first = surface; + if (prev == switcher->current) + next = surface; + prev = surface; + surface->alpha = 0.25; + weston_surface_geometry_dirty(surface); + weston_surface_damage(surface); + break; + default: + break; + } + + if (is_black_surface(surface, NULL)) { + surface->alpha = 0.25; + weston_surface_geometry_dirty(surface); + weston_surface_damage(surface); + } + } + + if (next == NULL) + next = first; + + if (next == NULL) + return; + + wl_list_remove(&switcher->listener.link); + wl_signal_add(&next->destroy_signal, &switcher->listener); + + switcher->current = next; + next->alpha = 1.0; + + shsurf = get_shell_surface(switcher->current); + if (shsurf && shsurf->type ==SHELL_SURFACE_FULLSCREEN) + shsurf->fullscreen.black_surface->alpha = 1.0; +} + +static void +switcher_handle_surface_destroy(struct wl_listener *listener, void *data) +{ + struct switcher *switcher = + container_of(listener, struct switcher, listener); + + switcher_next(switcher); +} + +static void +switcher_destroy(struct switcher *switcher) +{ + struct weston_surface *surface; + struct weston_keyboard *keyboard = switcher->grab.keyboard; + struct workspace *ws = get_current_workspace(switcher->shell); + + wl_list_for_each(surface, &ws->layer.surface_list, layer_link) { + surface->alpha = 1.0; + weston_surface_damage(surface); + } + + if (switcher->current) + activate(switcher->shell, switcher->current, + (struct weston_seat *) keyboard->seat); + wl_list_remove(&switcher->listener.link); + weston_keyboard_end_grab(keyboard); + if (keyboard->input_method_resource) + keyboard->grab = &keyboard->input_method_grab; + free(switcher); +} + +static void +switcher_key(struct weston_keyboard_grab *grab, + uint32_t time, uint32_t key, uint32_t state_w) +{ + struct switcher *switcher = container_of(grab, struct switcher, grab); + enum wl_keyboard_key_state state = state_w; + + if (key == KEY_TAB && state == WL_KEYBOARD_KEY_STATE_PRESSED) + switcher_next(switcher); +} + +static void +switcher_modifier(struct weston_keyboard_grab *grab, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + struct switcher *switcher = container_of(grab, struct switcher, grab); + struct weston_seat *seat = (struct weston_seat *) grab->keyboard->seat; + + if ((seat->modifier_state & switcher->shell->binding_modifier) == 0) + switcher_destroy(switcher); +} + +static void +switcher_cancel(struct weston_keyboard_grab *grab) +{ + struct switcher *switcher = container_of(grab, struct switcher, grab); + + switcher_destroy(switcher); +} + +static const struct weston_keyboard_grab_interface switcher_grab = { + switcher_key, + switcher_modifier, + switcher_cancel, +}; + +static void +switcher_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + struct desktop_shell *shell = data; + struct switcher *switcher; + + switcher = malloc(sizeof *switcher); + switcher->shell = shell; + switcher->current = NULL; + switcher->listener.notify = switcher_handle_surface_destroy; + wl_list_init(&switcher->listener.link); + + restore_all_output_modes(shell->compositor); + lower_fullscreen_layer(switcher->shell); + switcher->grab.interface = &switcher_grab; + weston_keyboard_start_grab(seat->keyboard, &switcher->grab); + weston_keyboard_set_focus(seat->keyboard, NULL); + switcher_next(switcher); +} + +static void +backlight_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + struct weston_compositor *compositor = data; + struct weston_output *output; + long backlight_new = 0; + + /* TODO: we're limiting to simple use cases, where we assume just + * control on the primary display. We'd have to extend later if we + * ever get support for setting backlights on random desktop LCD + * panels though */ + output = get_default_output(compositor); + if (!output) + return; + + if (!output->set_backlight) + return; + + if (key == KEY_F9 || key == KEY_BRIGHTNESSDOWN) + backlight_new = output->backlight_current - 25; + else if (key == KEY_F10 || key == KEY_BRIGHTNESSUP) + backlight_new = output->backlight_current + 25; + + if (backlight_new < 5) + backlight_new = 5; + if (backlight_new > 255) + backlight_new = 255; + + output->backlight_current = backlight_new; + output->set_backlight(output, output->backlight_current); +} + +struct debug_binding_grab { + struct weston_keyboard_grab grab; + struct weston_seat *seat; + uint32_t key[2]; + int key_released[2]; +}; + +static void +debug_binding_key(struct weston_keyboard_grab *grab, uint32_t time, + uint32_t key, uint32_t state) +{ + struct debug_binding_grab *db = (struct debug_binding_grab *) grab; + struct weston_compositor *ec = db->seat->compositor; + struct wl_display *display = ec->wl_display; + struct wl_resource *resource; + uint32_t serial; + int send = 0, terminate = 0; + int check_binding = 1; + int i; + struct wl_list *resource_list; + + if (state == WL_KEYBOARD_KEY_STATE_RELEASED) { + /* Do not run bindings on key releases */ + check_binding = 0; + + for (i = 0; i < 2; i++) + if (key == db->key[i]) + db->key_released[i] = 1; + + if (db->key_released[0] && db->key_released[1]) { + /* All key releases been swalled so end the grab */ + terminate = 1; + } else if (key != db->key[0] && key != db->key[1]) { + /* Should not swallow release of other keys */ + send = 1; + } + } else if (key == db->key[0] && !db->key_released[0]) { + /* Do not check bindings for the first press of the binding + * key. This allows it to be used as a debug shortcut. + * We still need to swallow this event. */ + check_binding = 0; + } else if (db->key[1]) { + /* If we already ran a binding don't process another one since + * we can't keep track of all the binding keys that were + * pressed in order to swallow the release events. */ + send = 1; + check_binding = 0; + } + + if (check_binding) { + if (weston_compositor_run_debug_binding(ec, db->seat, time, + key, state)) { + /* We ran a binding so swallow the press and keep the + * grab to swallow the released too. */ + send = 0; + terminate = 0; + db->key[1] = key; + } else { + /* Terminate the grab since the key pressed is not a + * debug binding key. */ + send = 1; + terminate = 1; + } + } + + if (send) { + serial = wl_display_next_serial(display); + resource_list = &grab->keyboard->focus_resource_list; + wl_resource_for_each(resource, resource_list) { + wl_keyboard_send_key(resource, serial, time, key, state); + } + } + + if (terminate) { + weston_keyboard_end_grab(grab->keyboard); + if (grab->keyboard->input_method_resource) + grab->keyboard->grab = &grab->keyboard->input_method_grab; + free(db); + } +} + +static void +debug_binding_modifiers(struct weston_keyboard_grab *grab, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + struct wl_resource *resource; + struct wl_list *resource_list; + + resource_list = &grab->keyboard->focus_resource_list; + + wl_resource_for_each(resource, resource_list) { + wl_keyboard_send_modifiers(resource, serial, mods_depressed, + mods_latched, mods_locked, group); + } +} + +static void +debug_binding_cancel(struct weston_keyboard_grab *grab) +{ + struct debug_binding_grab *db = (struct debug_binding_grab *) grab; + + weston_keyboard_end_grab(grab->keyboard); + free(db); +} + +struct weston_keyboard_grab_interface debug_binding_keyboard_grab = { + debug_binding_key, + debug_binding_modifiers, + debug_binding_cancel, +}; + +static void +debug_binding(struct weston_seat *seat, uint32_t time, uint32_t key, void *data) +{ + struct debug_binding_grab *grab; + + grab = calloc(1, sizeof *grab); + if (!grab) + return; + + grab->seat = (struct weston_seat *) seat; + grab->key[0] = key; + grab->grab.interface = &debug_binding_keyboard_grab; + weston_keyboard_start_grab(seat->keyboard, &grab->grab); +} + +static void +force_kill_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + struct weston_surface *focus_surface; + struct wl_client *client; + struct desktop_shell *shell = data; + struct weston_compositor *compositor = shell->compositor; + pid_t pid; + + focus_surface = seat->keyboard->focus; + if (!focus_surface) + return; + + wl_signal_emit(&compositor->kill_signal, focus_surface); + + client = wl_resource_get_client(focus_surface->resource); + wl_client_get_credentials(client, &pid, NULL, NULL); + + /* Skip clients that we launched ourselves (the credentials of + * the socketpair is ours) */ + if (pid == getpid()) + return; + + kill(pid, SIGKILL); +} + +static void +workspace_up_binding(struct weston_seat *seat, uint32_t time, + uint32_t key, void *data) +{ + struct desktop_shell *shell = data; + unsigned int new_index = shell->workspaces.current; + + if (shell->locked) + return; + if (new_index != 0) + new_index--; + + change_workspace(shell, new_index); +} + +static void +workspace_down_binding(struct weston_seat *seat, uint32_t time, + uint32_t key, void *data) +{ + struct desktop_shell *shell = data; + unsigned int new_index = shell->workspaces.current; + + if (shell->locked) + return; + if (new_index < shell->workspaces.num - 1) + new_index++; + + change_workspace(shell, new_index); +} + +static void +workspace_f_binding(struct weston_seat *seat, uint32_t time, + uint32_t key, void *data) +{ + struct desktop_shell *shell = data; + unsigned int new_index; + + if (shell->locked) + return; + new_index = key - KEY_F1; + if (new_index >= shell->workspaces.num) + new_index = shell->workspaces.num - 1; + + change_workspace(shell, new_index); +} + +static void +workspace_move_surface_up_binding(struct weston_seat *seat, uint32_t time, + uint32_t key, void *data) +{ + struct desktop_shell *shell = data; + unsigned int new_index = shell->workspaces.current; + + if (shell->locked) + return; + + if (new_index != 0) + new_index--; + + take_surface_to_workspace_by_seat(shell, seat, new_index); +} + +static void +workspace_move_surface_down_binding(struct weston_seat *seat, uint32_t time, + uint32_t key, void *data) +{ + struct desktop_shell *shell = data; + unsigned int new_index = shell->workspaces.current; + + if (shell->locked) + return; + + if (new_index < shell->workspaces.num - 1) + new_index++; + + take_surface_to_workspace_by_seat(shell, seat, new_index); +} + +static void +shell_destroy(struct wl_listener *listener, void *data) +{ + struct desktop_shell *shell = + container_of(listener, struct desktop_shell, destroy_listener); + struct workspace **ws; + + if (shell->child.client) + wl_client_destroy(shell->child.client); + + wl_list_remove(&shell->idle_listener.link); + wl_list_remove(&shell->wake_listener.link); + wl_list_remove(&shell->show_input_panel_listener.link); + wl_list_remove(&shell->hide_input_panel_listener.link); + + wl_array_for_each(ws, &shell->workspaces.array) + workspace_destroy(*ws); + wl_array_release(&shell->workspaces.array); + + free(shell->screensaver.path); + free(shell); +} + +static void +shell_add_bindings(struct weston_compositor *ec, struct desktop_shell *shell) +{ + uint32_t mod; + int i, num_workspace_bindings; + + /* fixed bindings */ + weston_compositor_add_key_binding(ec, KEY_BACKSPACE, + MODIFIER_CTRL | MODIFIER_ALT, + terminate_binding, ec); + weston_compositor_add_button_binding(ec, BTN_LEFT, 0, + click_to_activate_binding, + shell); + weston_compositor_add_touch_binding(ec, 0, + touch_to_activate_binding, + shell); + weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, + MODIFIER_SUPER | MODIFIER_ALT, + surface_opacity_binding, NULL); + weston_compositor_add_axis_binding(ec, WL_POINTER_AXIS_VERTICAL_SCROLL, + MODIFIER_SUPER, zoom_axis_binding, + NULL); + + /* configurable bindings */ + mod = shell->binding_modifier; + weston_compositor_add_key_binding(ec, KEY_PAGEUP, mod, + zoom_key_binding, NULL); + weston_compositor_add_key_binding(ec, KEY_PAGEDOWN, mod, + zoom_key_binding, NULL); + weston_compositor_add_button_binding(ec, BTN_LEFT, mod, move_binding, + shell); + weston_compositor_add_touch_binding(ec, mod, touch_move_binding, shell); + weston_compositor_add_button_binding(ec, BTN_MIDDLE, mod, + resize_binding, shell); + + if (ec->capabilities & WESTON_CAP_ROTATION_ANY) + weston_compositor_add_button_binding(ec, BTN_RIGHT, mod, + rotate_binding, NULL); + + weston_compositor_add_key_binding(ec, KEY_TAB, mod, switcher_binding, + shell); + weston_compositor_add_key_binding(ec, KEY_F9, mod, backlight_binding, + ec); + weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSDOWN, 0, + backlight_binding, ec); + weston_compositor_add_key_binding(ec, KEY_F10, mod, backlight_binding, + ec); + weston_compositor_add_key_binding(ec, KEY_BRIGHTNESSUP, 0, + backlight_binding, ec); + weston_compositor_add_key_binding(ec, KEY_K, mod, + force_kill_binding, shell); + weston_compositor_add_key_binding(ec, KEY_UP, mod, + workspace_up_binding, shell); + weston_compositor_add_key_binding(ec, KEY_DOWN, mod, + workspace_down_binding, shell); + weston_compositor_add_key_binding(ec, KEY_UP, mod | MODIFIER_SHIFT, + workspace_move_surface_up_binding, + shell); + weston_compositor_add_key_binding(ec, KEY_DOWN, mod | MODIFIER_SHIFT, + workspace_move_surface_down_binding, + shell); + + /* Add bindings for mod+F[1-6] for workspace 1 to 6. */ + if (shell->workspaces.num > 1) { + num_workspace_bindings = shell->workspaces.num; + if (num_workspace_bindings > 6) + num_workspace_bindings = 6; + for (i = 0; i < num_workspace_bindings; i++) + weston_compositor_add_key_binding(ec, KEY_F1 + i, mod, + workspace_f_binding, + shell); + } + + /* Debug bindings */ + weston_compositor_add_key_binding(ec, KEY_SPACE, mod | MODIFIER_SHIFT, + debug_binding, shell); +} + +WL_EXPORT int +module_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + struct weston_seat *seat; + struct desktop_shell *shell; + struct workspace **pws; + unsigned int i; + struct wl_event_loop *loop; + + shell = zalloc(sizeof *shell); + if (shell == NULL) + return -1; + + shell->compositor = ec; + + shell->destroy_listener.notify = shell_destroy; + wl_signal_add(&ec->destroy_signal, &shell->destroy_listener); + shell->idle_listener.notify = idle_handler; + wl_signal_add(&ec->idle_signal, &shell->idle_listener); + shell->wake_listener.notify = wake_handler; + wl_signal_add(&ec->wake_signal, &shell->wake_listener); + shell->show_input_panel_listener.notify = show_input_panels; + wl_signal_add(&ec->show_input_panel_signal, &shell->show_input_panel_listener); + shell->hide_input_panel_listener.notify = hide_input_panels; + wl_signal_add(&ec->hide_input_panel_signal, &shell->hide_input_panel_listener); + shell->update_input_panel_listener.notify = update_input_panels; + wl_signal_add(&ec->update_input_panel_signal, &shell->update_input_panel_listener); + ec->ping_handler = ping_handler; + ec->shell_interface.shell = shell; + ec->shell_interface.create_shell_surface = create_shell_surface; + ec->shell_interface.set_toplevel = set_toplevel; + ec->shell_interface.set_transient = set_transient; + ec->shell_interface.set_fullscreen = set_fullscreen; + ec->shell_interface.set_xwayland = set_xwayland; + ec->shell_interface.move = surface_move; + ec->shell_interface.resize = surface_resize; + ec->shell_interface.set_title = set_title; + + wl_list_init(&shell->input_panel.surfaces); + + weston_layer_init(&shell->fullscreen_layer, &ec->cursor_layer.link); + weston_layer_init(&shell->panel_layer, &shell->fullscreen_layer.link); + weston_layer_init(&shell->background_layer, &shell->panel_layer.link); + weston_layer_init(&shell->lock_layer, NULL); + weston_layer_init(&shell->input_panel_layer, NULL); + + wl_array_init(&shell->workspaces.array); + wl_list_init(&shell->workspaces.client_list); + + shell_configuration(shell); + + for (i = 0; i < shell->workspaces.num; i++) { + pws = wl_array_add(&shell->workspaces.array, sizeof *pws); + if (pws == NULL) + return -1; + + *pws = workspace_create(); + if (*pws == NULL) + return -1; + } + activate_workspace(shell, 0); + + wl_list_init(&shell->workspaces.anim_sticky_list); + wl_list_init(&shell->workspaces.animation.link); + shell->workspaces.animation.frame = animate_workspace_change_frame; + + if (wl_global_create(ec->wl_display, &wl_shell_interface, 1, + shell, bind_shell) == NULL) + return -1; + + if (wl_global_create(ec->wl_display, + &desktop_shell_interface, 2, + shell, bind_desktop_shell) == NULL) + return -1; + + if (wl_global_create(ec->wl_display, &screensaver_interface, 1, + shell, bind_screensaver) == NULL) + return -1; + + if (wl_global_create(ec->wl_display, &wl_input_panel_interface, 1, + shell, bind_input_panel) == NULL) + return -1; + + if (wl_global_create(ec->wl_display, &workspace_manager_interface, 1, + shell, bind_workspace_manager) == NULL) + return -1; + + shell->child.deathstamp = weston_compositor_get_time(); + + loop = wl_display_get_event_loop(ec->wl_display); + wl_event_loop_add_idle(loop, launch_desktop_shell_process, shell); + + shell->screensaver.timer = + wl_event_loop_add_timer(loop, screensaver_timeout, shell); + + wl_list_for_each(seat, &ec->seat_list, link) + create_pointer_focus_listener(seat); + + shell_add_bindings(ec, shell); + + shell_fade_init(shell); + + return 0; +} diff --git a/src/spring-tool.c b/src/spring-tool.c new file mode 100644 index 00000000..935acc4b --- /dev/null +++ b/src/spring-tool.c @@ -0,0 +1,66 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include "compositor.h" + +WL_EXPORT void +weston_surface_geometry_dirty(struct weston_surface *surface) +{ +} + +WL_EXPORT int +weston_log(const char *fmt, ...) +{ + return 0; +} + +WL_EXPORT void +weston_compositor_schedule_repaint(struct weston_compositor *compositor) +{ +} + +int +main(int argc, char *argv[]) +{ + const double k = 300.0; + const double current = 0.5; + const double target = 1.0; + const double friction = 1400; + + struct weston_spring spring; + uint32_t time = 0; + + weston_spring_init(&spring, k, current, target); + spring.friction = friction; + spring.previous = 0.48; + spring.timestamp = 0; + + while (!weston_spring_done(&spring)) { + printf("\t%d\t%f\n", time, spring.current); + weston_spring_update(&spring, time); + time += 16; + } + + return 0; +} diff --git a/src/tablet-shell.c b/src/tablet-shell.c new file mode 100644 index 00000000..b055ab23 --- /dev/null +++ b/src/tablet-shell.c @@ -0,0 +1,575 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "compositor.h" +#include "tablet-shell-server-protocol.h" + +/* + * TODO: Don't fade back from black until we've received a lockscreen + * attachment. + */ + +enum { + STATE_STARTING, + STATE_LOCKED, + STATE_HOME, + STATE_SWITCHER, + STATE_TASK +}; + +struct tablet_shell { + struct wl_resource *resource; + + struct wl_listener lock_listener; + struct wl_listener unlock_listener; + struct wl_listener destroy_listener; + + struct weston_compositor *compositor; + struct weston_process process; + struct wl_client *client; + + struct weston_surface *surface; + + struct weston_surface *lockscreen_surface; + struct wl_listener lockscreen_listener; + struct weston_layer lockscreen_layer; + + struct weston_layer application_layer; + + struct weston_surface *home_surface; + struct weston_layer homescreen_layer; + + struct weston_surface *switcher_surface; + struct wl_listener switcher_listener; + + struct tablet_client *current_client; + + int state, previous_state; + int long_press_active; + struct wl_event_source *long_press_source; +}; + +struct tablet_client { + struct wl_resource *resource; + struct tablet_shell *shell; + struct wl_client *client; + struct weston_surface *surface; + char *name; +}; + +static void +tablet_shell_destroy(struct wl_listener *listener, void *data); + +static struct tablet_shell * +get_shell(struct weston_compositor *compositor) +{ + struct wl_listener *l; + + l = wl_signal_get(&compositor->destroy_signal, tablet_shell_destroy); + if (l) + return container_of(l, struct tablet_shell, destroy_listener); + + return NULL; +} + +static void +tablet_shell_sigchld(struct weston_process *process, int status) +{ + struct tablet_shell *shell = + container_of(process, struct tablet_shell, process); + + shell->process.pid = 0; + + weston_log("weston-tablet-shell crashed, exit code %d\n", status); +} + +static void +tablet_shell_set_state(struct tablet_shell *shell, int state) +{ + static const char *states[] = { + "STARTING", "LOCKED", "HOME", "SWITCHER", "TASK" + }; + + weston_log("switching to state %s (from %s)\n", + states[state], states[shell->state]); + shell->previous_state = shell->state; + shell->state = state; +} + +static void +tablet_shell_surface_configure(struct weston_surface *surface, + int32_t sx, int32_t sy, int32_t width, int32_t height) +{ + struct tablet_shell *shell = get_shell(surface->compositor); + + if (weston_surface_is_mapped(surface) || width == 0) + return; + + weston_surface_configure(surface, 0, 0, width, height); + + if (surface == shell->lockscreen_surface) { + wl_list_insert(&shell->lockscreen_layer.surface_list, + &surface->layer_link); + } else if (surface == shell->switcher_surface) { + /* */ + } else if (surface == shell->home_surface) { + if (shell->state == STATE_STARTING) { + /* homescreen always visible, at the bottom */ + wl_list_insert(&shell->homescreen_layer.surface_list, + &surface->layer_link); + + tablet_shell_set_state(shell, STATE_LOCKED); + shell->previous_state = STATE_HOME; + tablet_shell_send_show_lockscreen(shell->resource); + } + } else if (shell->current_client && + shell->current_client->surface != surface && + shell->current_client->client == wl_resource_get_client(surface->resource)) { + tablet_shell_set_state(shell, STATE_TASK); + shell->current_client->surface = surface; + weston_zoom_run(surface, 0.3, 1.0, NULL, NULL); + wl_list_insert(&shell->application_layer.surface_list, + &surface->layer_link); + } + + weston_surface_update_transform(surface); +} + +static void +handle_lockscreen_surface_destroy(struct wl_listener *listener, void *data) +{ + struct tablet_shell *shell = + container_of(listener, + struct tablet_shell, lockscreen_listener); + + shell->lockscreen_surface = NULL; + tablet_shell_set_state(shell, shell->previous_state); +} + +static void +tablet_shell_set_lockscreen(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource) +{ + struct tablet_shell *shell = wl_resource_get_user_data(resource); + struct weston_surface *es = wl_resource_get_user_data(surface_resource); + + weston_surface_set_position(es, 0, 0); + shell->lockscreen_surface = es; + shell->lockscreen_surface->configure = tablet_shell_surface_configure; + shell->lockscreen_listener.notify = handle_lockscreen_surface_destroy; + wl_signal_add(&es->destroy_signal, &shell->lockscreen_listener); +} + +static void +handle_switcher_surface_destroy(struct wl_listener *listener, void *data) +{ + struct tablet_shell *shell = + container_of(listener, + struct tablet_shell, switcher_listener); + + shell->switcher_surface = NULL; + if (shell->state != STATE_LOCKED) + tablet_shell_set_state(shell, shell->previous_state); +} + +static void +tablet_shell_set_switcher(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource) +{ + struct tablet_shell *shell = wl_resource_get_user_data(resource); + struct weston_surface *es = wl_resource_get_user_data(surface_resource); + + /* FIXME: Switcher should be centered and the compositor + * should do the tinting of the background. With the cache + * layer idea, we should be able to hit the framerate on the + * fade/zoom in. */ + shell->switcher_surface = es; + weston_surface_set_position(shell->switcher_surface, 0, 0); + + shell->switcher_listener.notify = handle_switcher_surface_destroy; + wl_signal_add(&es->destroy_signal, &shell->switcher_listener); +} + +static void +tablet_shell_set_homescreen(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource) +{ + struct tablet_shell *shell = wl_resource_get_user_data(resource); + + shell->home_surface = wl_resource_get_user_data(surface_resource); + shell->home_surface->configure = tablet_shell_surface_configure; + + weston_surface_set_position(shell->home_surface, 0, 0); +} + +static void +minimize_zoom_done(struct weston_surface_animation *zoom, void *data) +{ + struct tablet_shell *shell = data; + struct weston_compositor *compositor = shell->compositor; + struct weston_seat *seat; + + wl_list_for_each(seat, &compositor->seat_list, link) + weston_surface_activate(shell->home_surface, seat); +} + +static void +tablet_shell_switch_to(struct tablet_shell *shell, + struct weston_surface *surface) +{ + struct weston_compositor *compositor = shell->compositor; + struct weston_seat *seat; + struct weston_surface *current; + + if (shell->state == STATE_SWITCHER) { + wl_list_remove(&shell->switcher_listener.link); + shell->switcher_surface = NULL; + }; + + if (surface == shell->home_surface) { + tablet_shell_set_state(shell, STATE_HOME); + + if (shell->current_client && shell->current_client->surface) { + current = shell->current_client->surface; + weston_zoom_run(current, 1.0, 0.3, + minimize_zoom_done, shell); + } + } else { + fprintf(stderr, "switch to %p\n", surface); + wl_list_for_each(seat, &compositor->seat_list, link) + weston_surface_activate(surface, seat); + tablet_shell_set_state(shell, STATE_TASK); + weston_zoom_run(surface, 0.3, 1.0, NULL, NULL); + } +} + +static void +tablet_shell_show_grid(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource) +{ + struct tablet_shell *shell = wl_resource_get_user_data(resource); + struct weston_surface *es = wl_resource_get_user_data(surface_resource); + + tablet_shell_switch_to(shell, es); +} + +static void +tablet_shell_show_panels(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource) +{ + struct tablet_shell *shell = wl_resource_get_user_data(resource); + struct weston_surface *es = wl_resource_get_user_data(surface_resource); + + tablet_shell_switch_to(shell, es); +} + +static void +destroy_tablet_client(struct wl_resource *resource) +{ + struct tablet_client *tablet_client = + wl_resource_get_user_data(resource); + + free(tablet_client->name); + free(tablet_client); +} + +static void +tablet_client_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +tablet_client_activate(struct wl_client *client, struct wl_resource *resource) +{ + struct tablet_client *tablet_client = wl_resource_get_user_data(resource); + struct tablet_shell *shell = tablet_client->shell; + + shell->current_client = tablet_client; + if (!tablet_client->surface) + return; + + tablet_shell_switch_to(shell, tablet_client->surface); +} + +static const struct tablet_client_interface tablet_client_implementation = { + tablet_client_destroy, + tablet_client_activate +}; + +static void +tablet_shell_create_client(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, const char *name, int fd) +{ + struct tablet_shell *shell = wl_resource_get_user_data(resource); + struct weston_compositor *compositor = shell->compositor; + struct tablet_client *tablet_client; + + tablet_client = malloc(sizeof *tablet_client); + if (tablet_client == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + tablet_client->client = wl_client_create(compositor->wl_display, fd); + tablet_client->shell = shell; + tablet_client->name = strdup(name); + + tablet_client->resource = + wl_resource_create(client, &tablet_client_interface, 1, id); + wl_resource_set_implementation(tablet_client->resource, + &tablet_client_implementation, + tablet_client, destroy_tablet_client); + + tablet_client->surface = NULL; + shell->current_client = tablet_client; + + weston_log("created client %p, id %d, name %s, fd %d\n", + tablet_client->client, id, name, fd); +} + +static const struct tablet_shell_interface tablet_shell_implementation = { + tablet_shell_set_lockscreen, + tablet_shell_set_switcher, + tablet_shell_set_homescreen, + tablet_shell_show_grid, + tablet_shell_show_panels, + tablet_shell_create_client +}; + +static void +launch_ux_daemon(struct tablet_shell *shell) +{ + const char *shell_exe = LIBEXECDIR "/weston-tablet-shell"; + + shell->client = weston_client_launch(shell->compositor, + &shell->process, + shell_exe, tablet_shell_sigchld); +} + +static void +toggle_switcher(struct tablet_shell *shell) +{ + switch (shell->state) { + case STATE_SWITCHER: + tablet_shell_send_hide_switcher(shell->resource); + break; + default: + tablet_shell_send_show_switcher(shell->resource); + tablet_shell_set_state(shell, STATE_SWITCHER); + break; + } +} + +static void +tablet_shell_lock(struct wl_listener *listener, void *data) +{ + struct tablet_shell *shell = + container_of(listener, struct tablet_shell, lock_listener); + + if (shell->state == STATE_LOCKED) + return; + if (shell->state == STATE_SWITCHER) + tablet_shell_send_hide_switcher(shell->resource); + + tablet_shell_send_show_lockscreen(shell->resource); + tablet_shell_set_state(shell, STATE_LOCKED); +} + +static void +tablet_shell_unlock(struct wl_listener *listener, void *data) +{ + struct tablet_shell *shell = + container_of(listener, struct tablet_shell, unlock_listener); + + tablet_shell_set_state(shell, STATE_HOME); +} + +static void +go_home(struct tablet_shell *shell, struct weston_seat *seat) +{ + if (shell->state == STATE_SWITCHER) + tablet_shell_send_hide_switcher(shell->resource); + + weston_surface_activate(shell->home_surface, seat); + + tablet_shell_set_state(shell, STATE_HOME); +} + +static int +long_press_handler(void *data) +{ + struct tablet_shell *shell = data; + + shell->long_press_active = 0; + toggle_switcher(shell); + + return 1; +} + +static void +menu_key_binding(struct weston_seat *seat, uint32_t time, uint32_t key, void *data) +{ + struct tablet_shell *shell = data; + + if (shell->state == STATE_LOCKED) + return; + + toggle_switcher(shell); +} + +static void +home_key_binding(struct weston_seat *seat, uint32_t time, uint32_t key, void *data) +{ + struct tablet_shell *shell = data; + + if (shell->state == STATE_LOCKED) + return; + + if (1) { + wl_event_source_timer_update(shell->long_press_source, 500); + shell->long_press_active = 1; + } else if (shell->long_press_active) { + /* This code has never been run ... */ + wl_event_source_timer_update(shell->long_press_source, 0); + shell->long_press_active = 0; + + switch (shell->state) { + case STATE_HOME: + case STATE_SWITCHER: + toggle_switcher(shell); + break; + default: + go_home(shell, (struct weston_seat *) seat); + break; + } + } +} + +static void +destroy_tablet_shell(struct wl_resource *resource) +{ +} + +static void +bind_tablet_shell(struct wl_client *client, void *data, uint32_t version, + uint32_t id) +{ + struct tablet_shell *shell = data; + + if (shell->client != client) + /* Throw an error or just let the client fail when it + * tries to access the object?. */ + return; + + shell->resource = + wl_resource_create(client, &tablet_shell_interface, 1, id); + wl_resource_set_implementation(shell->resource, + &tablet_shell_implementation, + shell, destroy_tablet_shell); +} + +static void +tablet_shell_destroy(struct wl_listener *listener, void *data) +{ + struct tablet_shell *shell = + container_of(listener, struct tablet_shell, destroy_listener); + + if (shell->home_surface) + shell->home_surface->configure = NULL; + + if (shell->lockscreen_surface) + shell->lockscreen_surface->configure = NULL; + + wl_event_source_remove(shell->long_press_source); + free(shell); +} + +WL_EXPORT int +module_init(struct weston_compositor *compositor, + int *argc, char *argv[]) +{ + struct tablet_shell *shell; + struct wl_event_loop *loop; + + shell = zalloc(sizeof *shell); + if (shell == NULL) + return -1; + + shell->compositor = compositor; + + shell->destroy_listener.notify = tablet_shell_destroy; + wl_signal_add(&compositor->destroy_signal, &shell->destroy_listener); + shell->lock_listener.notify = tablet_shell_lock; + wl_signal_add(&compositor->idle_signal, &shell->lock_listener); + shell->unlock_listener.notify = tablet_shell_unlock; + wl_signal_add(&compositor->wake_signal, &shell->unlock_listener); + + /* FIXME: This will make the object available to all clients. */ + wl_global_create(compositor->wl_display, &tablet_shell_interface, 1, + shell, bind_tablet_shell); + + loop = wl_display_get_event_loop(compositor->wl_display); + shell->long_press_source = + wl_event_loop_add_timer(loop, long_press_handler, shell); + + weston_compositor_add_key_binding(compositor, KEY_LEFTMETA, 0, + home_key_binding, shell); + weston_compositor_add_key_binding(compositor, KEY_RIGHTMETA, 0, + home_key_binding, shell); + weston_compositor_add_key_binding(compositor, KEY_LEFTMETA, + MODIFIER_SUPER, home_key_binding, + shell); + weston_compositor_add_key_binding(compositor, KEY_RIGHTMETA, + MODIFIER_SUPER, home_key_binding, + shell); + weston_compositor_add_key_binding(compositor, KEY_COMPOSE, 0, + menu_key_binding, shell); + + weston_layer_init(&shell->homescreen_layer, + &compositor->cursor_layer.link); + weston_layer_init(&shell->application_layer, + &compositor->cursor_layer.link); + weston_layer_init(&shell->lockscreen_layer, + &compositor->cursor_layer.link); + launch_ux_daemon(shell); + + tablet_shell_set_state(shell, STATE_STARTING); + + return 0; +} diff --git a/src/text-backend.c b/src/text-backend.c new file mode 100644 index 00000000..107ccd63 --- /dev/null +++ b/src/text-backend.c @@ -0,0 +1,967 @@ +/* + * Copyright © 2012 Openismus GmbH + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "compositor.h" +#include "text-server-protocol.h" +#include "input-method-server-protocol.h" + +struct input_method; +struct input_method_context; +struct text_backend; + +struct text_input { + struct wl_resource *resource; + + struct weston_compositor *ec; + + struct wl_list input_methods; + + struct weston_surface *surface; + + pixman_box32_t cursor_rectangle; + + uint32_t input_panel_visible; +}; + +struct text_input_manager { + struct wl_global *text_input_manager_global; + struct wl_listener destroy_listener; + + struct weston_compositor *ec; +}; + +struct input_method { + struct wl_resource *input_method_binding; + struct wl_global *input_method_global; + struct wl_listener destroy_listener; + + struct weston_seat *seat; + struct text_input *model; + + struct wl_list link; + + struct wl_listener keyboard_focus_listener; + + int focus_listener_initialized; + + struct input_method_context *context; + + struct text_backend *text_backend; +}; + +struct input_method_context { + struct wl_resource *resource; + + struct text_input *model; + struct input_method *input_method; + + struct wl_list link; + + struct wl_resource *keyboard; +}; + +struct text_backend { + struct weston_compositor *compositor; + + struct { + char *path; + struct wl_resource *binding; + struct weston_process process; + struct wl_client *client; + + unsigned deathcount; + uint32_t deathstamp; + } input_method; + + struct wl_listener seat_created_listener; + struct wl_listener destroy_listener; +}; + +static void input_method_context_create(struct text_input *model, + struct input_method *input_method); +static void input_method_context_end_keyboard_grab(struct input_method_context *context); + +static void input_method_init_seat(struct weston_seat *seat); + +static void +deactivate_text_input(struct text_input *text_input, + struct input_method *input_method) +{ + struct weston_compositor *ec = text_input->ec; + + if (input_method->model == text_input) { + if (input_method->context && input_method->input_method_binding) { + input_method_context_end_keyboard_grab(input_method->context); + wl_input_method_send_deactivate(input_method->input_method_binding, + input_method->context->resource); + } + + wl_list_remove(&input_method->link); + input_method->model = NULL; + input_method->context = NULL; + wl_signal_emit(&ec->hide_input_panel_signal, ec); + wl_text_input_send_leave(text_input->resource); + } +} + +static void +destroy_text_input(struct wl_resource *resource) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct input_method *input_method, *next; + + wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) + deactivate_text_input(text_input, input_method); + + free(text_input); +} + +static void +text_input_set_surrounding_text(struct wl_client *client, + struct wl_resource *resource, + const char *text, + uint32_t cursor, + uint32_t anchor) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct input_method *input_method, *next; + + wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { + if (!input_method->context) + continue; + wl_input_method_context_send_surrounding_text(input_method->context->resource, + text, + cursor, + anchor); + } +} + +static void +text_input_activate(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + struct wl_resource *surface) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct weston_seat *weston_seat = wl_resource_get_user_data(seat); + struct input_method *input_method = weston_seat->input_method; + struct text_input *old = weston_seat->input_method->model; + struct weston_compositor *ec = text_input->ec; + + if (old == text_input) + return; + + if (old) { + deactivate_text_input(old, + weston_seat->input_method); + } + + input_method->model = text_input; + wl_list_insert(&text_input->input_methods, &input_method->link); + input_method_init_seat(weston_seat); + + text_input->surface = wl_resource_get_user_data(surface); + + input_method_context_create(text_input, input_method); + + if (text_input->input_panel_visible) { + wl_signal_emit(&ec->show_input_panel_signal, text_input->surface); + wl_signal_emit(&ec->update_input_panel_signal, &text_input->cursor_rectangle); + } + + wl_text_input_send_enter(text_input->resource, text_input->surface->resource); +} + +static void +text_input_deactivate(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct weston_seat *weston_seat = wl_resource_get_user_data(seat); + + deactivate_text_input(text_input, + weston_seat->input_method); +} + +static void +text_input_reset(struct wl_client *client, + struct wl_resource *resource) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct input_method *input_method, *next; + + wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { + if (!input_method->context) + continue; + wl_input_method_context_send_reset(input_method->context->resource); + } +} + +static void +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 text_input *text_input = wl_resource_get_user_data(resource); + struct weston_compositor *ec = text_input->ec; + + text_input->cursor_rectangle.x1 = x; + text_input->cursor_rectangle.y1 = y; + text_input->cursor_rectangle.x2 = x + width; + text_input->cursor_rectangle.y2 = y + height; + + wl_signal_emit(&ec->update_input_panel_signal, &text_input->cursor_rectangle); +} + +static void +text_input_set_content_type(struct wl_client *client, + struct wl_resource *resource, + uint32_t hint, + uint32_t purpose) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct input_method *input_method, *next; + + wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { + if (!input_method->context) + continue; + wl_input_method_context_send_content_type(input_method->context->resource, hint, purpose); + } +} + +static void +text_input_invoke_action(struct wl_client *client, + struct wl_resource *resource, + uint32_t button, + uint32_t index) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct input_method *input_method, *next; + + wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { + if (!input_method->context) + continue; + wl_input_method_context_send_invoke_action(input_method->context->resource, button, index); + } +} + +static void +text_input_commit_state(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct input_method *input_method, *next; + + wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { + if (!input_method->context) + continue; + wl_input_method_context_send_commit_state(input_method->context->resource, serial); + } +} + +static void +text_input_show_input_panel(struct wl_client *client, + struct wl_resource *resource) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct weston_compositor *ec = text_input->ec; + + text_input->input_panel_visible = 1; + + if (!wl_list_empty(&text_input->input_methods)) { + wl_signal_emit(&ec->show_input_panel_signal, text_input->surface); + wl_signal_emit(&ec->update_input_panel_signal, &text_input->cursor_rectangle); + } +} + +static void +text_input_hide_input_panel(struct wl_client *client, + struct wl_resource *resource) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct weston_compositor *ec = text_input->ec; + + text_input->input_panel_visible = 0; + + if (!wl_list_empty(&text_input->input_methods)) + wl_signal_emit(&ec->hide_input_panel_signal, ec); +} + +static void +text_input_set_preferred_language(struct wl_client *client, + struct wl_resource *resource, + const char *language) +{ + struct text_input *text_input = wl_resource_get_user_data(resource); + struct input_method *input_method, *next; + + wl_list_for_each_safe(input_method, next, &text_input->input_methods, link) { + if (!input_method->context) + continue; + wl_input_method_context_send_preferred_language(input_method->context->resource, + language); + } +} + +static const struct wl_text_input_interface text_input_implementation = { + text_input_activate, + text_input_deactivate, + text_input_show_input_panel, + text_input_hide_input_panel, + text_input_reset, + text_input_set_surrounding_text, + text_input_set_content_type, + text_input_set_cursor_rectangle, + text_input_set_preferred_language, + text_input_commit_state, + text_input_invoke_action +}; + +static void text_input_manager_create_text_input(struct wl_client *client, + struct wl_resource *resource, + uint32_t id) +{ + struct text_input_manager *text_input_manager = wl_resource_get_user_data(resource); + struct text_input *text_input; + + text_input = calloc(1, sizeof *text_input); + + text_input->resource = + wl_resource_create(client, &wl_text_input_interface, 1, id); + wl_resource_set_implementation(text_input->resource, + &text_input_implementation, + text_input, destroy_text_input); + + text_input->ec = text_input_manager->ec; + + wl_list_init(&text_input->input_methods); +}; + +static const struct wl_text_input_manager_interface text_input_manager_implementation = { + text_input_manager_create_text_input +}; + +static void +bind_text_input_manager(struct wl_client *client, + void *data, + uint32_t version, + uint32_t id) +{ + struct text_input_manager *text_input_manager = data; + struct wl_resource *resource; + + /* No checking for duplicate binding necessary. */ + resource = + wl_resource_create(client, + &wl_text_input_manager_interface, 1, id); + if (resource) + wl_resource_set_implementation(resource, + &text_input_manager_implementation, + text_input_manager, NULL); +} + +static void +text_input_manager_notifier_destroy(struct wl_listener *listener, void *data) +{ + struct text_input_manager *text_input_manager = + container_of(listener, struct text_input_manager, destroy_listener); + + wl_global_destroy(text_input_manager->text_input_manager_global); + + free(text_input_manager); +} + +static void +text_input_manager_create(struct weston_compositor *ec) +{ + struct text_input_manager *text_input_manager; + + text_input_manager = calloc(1, sizeof *text_input_manager); + + text_input_manager->ec = ec; + + text_input_manager->text_input_manager_global = + wl_global_create(ec->wl_display, + &wl_text_input_manager_interface, 1, + text_input_manager, bind_text_input_manager); + + text_input_manager->destroy_listener.notify = text_input_manager_notifier_destroy; + wl_signal_add(&ec->destroy_signal, &text_input_manager->destroy_listener); +} + +static void +input_method_context_destroy(struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy(resource); +} + +static void +input_method_context_commit_string(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + const char *text) +{ + struct input_method_context *context = + wl_resource_get_user_data(resource); + + wl_text_input_send_commit_string(context->model->resource, serial, text); +} + +static void +input_method_context_preedit_string(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + const char *text, + const char *commit) +{ + struct input_method_context *context = + wl_resource_get_user_data(resource); + + wl_text_input_send_preedit_string(context->model->resource, serial, text, commit); +} + +static void +input_method_context_preedit_styling(struct wl_client *client, + struct wl_resource *resource, + uint32_t index, + uint32_t length, + uint32_t style) +{ + struct input_method_context *context = + wl_resource_get_user_data(resource); + + wl_text_input_send_preedit_styling(context->model->resource, index, length, style); +} + +static void +input_method_context_preedit_cursor(struct wl_client *client, + struct wl_resource *resource, + int32_t cursor) +{ + struct input_method_context *context = + wl_resource_get_user_data(resource); + + wl_text_input_send_preedit_cursor(context->model->resource, cursor); +} + +static void +input_method_context_delete_surrounding_text(struct wl_client *client, + struct wl_resource *resource, + int32_t index, + uint32_t length) +{ + struct input_method_context *context = + wl_resource_get_user_data(resource); + + wl_text_input_send_delete_surrounding_text(context->model->resource, index, length); +} + +static void +input_method_context_cursor_position(struct wl_client *client, + struct wl_resource *resource, + int32_t index, + int32_t anchor) +{ + struct input_method_context *context = + wl_resource_get_user_data(resource); + + wl_text_input_send_cursor_position(context->model->resource, index, anchor); +} + +static void +input_method_context_modifiers_map(struct wl_client *client, + struct wl_resource *resource, + struct wl_array *map) +{ + struct input_method_context *context = + wl_resource_get_user_data(resource); + + wl_text_input_send_modifiers_map(context->model->resource, map); +} + +static void +input_method_context_keysym(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + uint32_t time, + uint32_t sym, + uint32_t state, + uint32_t modifiers) +{ + struct input_method_context *context = + wl_resource_get_user_data(resource); + + wl_text_input_send_keysym(context->model->resource, serial, time, + sym, state, modifiers); +} + +static void +unbind_keyboard(struct wl_resource *resource) +{ + struct input_method_context *context = + wl_resource_get_user_data(resource); + + input_method_context_end_keyboard_grab(context); + context->keyboard = NULL; +} + +static void +input_method_context_grab_key(struct weston_keyboard_grab *grab, + uint32_t time, uint32_t key, uint32_t state_w) +{ + struct weston_keyboard *keyboard = grab->keyboard; + struct wl_display *display; + uint32_t serial; + + if (!keyboard->input_method_resource) + return; + + display = wl_client_get_display(wl_resource_get_client(keyboard->input_method_resource)); + serial = wl_display_next_serial(display); + wl_keyboard_send_key(keyboard->input_method_resource, + serial, time, key, state_w); +} + +static void +input_method_context_grab_modifier(struct weston_keyboard_grab *grab, uint32_t serial, + uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + struct weston_keyboard *keyboard = grab->keyboard; + + if (!keyboard->input_method_resource) + return; + + wl_keyboard_send_modifiers(keyboard->input_method_resource, + serial, mods_depressed, mods_latched, + mods_locked, group); +} + +static void +input_method_context_grab_cancel(struct weston_keyboard_grab *grab) +{ + weston_keyboard_end_grab(grab->keyboard); +} + +static const struct weston_keyboard_grab_interface input_method_context_grab = { + input_method_context_grab_key, + input_method_context_grab_modifier, + input_method_context_grab_cancel, +}; + +static void +input_method_context_grab_keyboard(struct wl_client *client, + struct wl_resource *resource, + uint32_t id) +{ + struct input_method_context *context = wl_resource_get_user_data(resource); + struct wl_resource *cr; + struct weston_seat *seat = context->input_method->seat; + struct weston_keyboard *keyboard = seat->keyboard; + + cr = wl_resource_create(client, &wl_keyboard_interface, 1, id); + wl_resource_set_implementation(cr, NULL, context, unbind_keyboard); + + context->keyboard = cr; + + wl_keyboard_send_keymap(cr, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, + seat->xkb_info->keymap_fd, + seat->xkb_info->keymap_size); + + if (keyboard->grab != &keyboard->default_grab) { + weston_keyboard_end_grab(keyboard); + } + weston_keyboard_start_grab(keyboard, &keyboard->input_method_grab); + keyboard->input_method_resource = cr; +} + +static void +input_method_context_key(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state_w) +{ + struct input_method_context *context = wl_resource_get_user_data(resource); + struct weston_seat *seat = context->input_method->seat; + struct weston_keyboard *keyboard = seat->keyboard; + struct weston_keyboard_grab *default_grab = &keyboard->default_grab; + + default_grab->interface->key(default_grab, time, key, state_w); +} + +static void +input_method_context_modifiers(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) +{ + struct input_method_context *context = wl_resource_get_user_data(resource); + + struct weston_seat *seat = context->input_method->seat; + struct weston_keyboard *keyboard = seat->keyboard; + struct weston_keyboard_grab *default_grab = &keyboard->default_grab; + + default_grab->interface->modifiers(default_grab, + serial, mods_depressed, + mods_latched, mods_locked, + group); +} + +static void +input_method_context_language(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + const char *language) +{ + struct input_method_context *context = wl_resource_get_user_data(resource); + + wl_text_input_send_language(context->model->resource, serial, language); +} + +static void +input_method_context_text_direction(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial, + uint32_t direction) +{ + struct input_method_context *context = wl_resource_get_user_data(resource); + + wl_text_input_send_text_direction(context->model->resource, serial, direction); +} + + +static const struct wl_input_method_context_interface input_method_context_implementation = { + input_method_context_destroy, + input_method_context_commit_string, + input_method_context_preedit_string, + input_method_context_preedit_styling, + input_method_context_preedit_cursor, + input_method_context_delete_surrounding_text, + input_method_context_cursor_position, + input_method_context_modifiers_map, + input_method_context_keysym, + input_method_context_grab_keyboard, + input_method_context_key, + input_method_context_modifiers, + input_method_context_language, + input_method_context_text_direction +}; + +static void +destroy_input_method_context(struct wl_resource *resource) +{ + struct input_method_context *context = wl_resource_get_user_data(resource); + + if (context->keyboard) { + wl_resource_destroy(context->keyboard); + } + + free(context); +} + +static void +input_method_context_create(struct text_input *model, + struct input_method *input_method) +{ + struct input_method_context *context; + struct wl_resource *binding; + + if (!input_method->input_method_binding) + return; + + context = calloc(1, sizeof *context); + if (context == NULL) + return; + + binding = input_method->input_method_binding; + context->resource = + wl_resource_create(wl_resource_get_client(binding), + &wl_input_method_context_interface, 1, 0); + wl_resource_set_implementation(context->resource, + &input_method_context_implementation, + context, destroy_input_method_context); + + context->model = model; + context->input_method = input_method; + input_method->context = context; + + + wl_input_method_send_activate(binding, context->resource); +} + +static void +input_method_context_end_keyboard_grab(struct input_method_context *context) +{ + struct weston_keyboard_grab *grab = + &context->input_method->seat->keyboard->input_method_grab; + struct weston_keyboard *keyboard = grab->keyboard; + + if (!grab->keyboard) + return; + + if (grab->keyboard->grab == grab) + weston_keyboard_end_grab(grab->keyboard); + + keyboard->input_method_resource = NULL; +} + +static void +unbind_input_method(struct wl_resource *resource) +{ + struct input_method *input_method = wl_resource_get_user_data(resource); + struct text_backend *text_backend = input_method->text_backend; + + input_method->input_method_binding = NULL; + input_method->context = NULL; + + text_backend->input_method.binding = NULL; +} + +static void +bind_input_method(struct wl_client *client, + void *data, + uint32_t version, + uint32_t id) +{ + struct input_method *input_method = data; + struct text_backend *text_backend = input_method->text_backend; + struct wl_resource *resource; + + resource = + wl_resource_create(client, &wl_input_method_interface, 1, id); + + if (input_method->input_method_binding != NULL) { + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "interface object already bound"); + wl_resource_destroy(resource); + return; + } + + if (text_backend->input_method.client != client) { + wl_resource_post_error(resource, WL_DISPLAY_ERROR_INVALID_OBJECT, + "permission to bind input_method denied"); + wl_resource_destroy(resource); + return; + } + + wl_resource_set_implementation(resource, NULL, input_method, + unbind_input_method); + input_method->input_method_binding = resource; + + text_backend->input_method.binding = resource; +} + +static void +input_method_notifier_destroy(struct wl_listener *listener, void *data) +{ + struct input_method *input_method = + container_of(listener, struct input_method, destroy_listener); + + if (input_method->model) + deactivate_text_input(input_method->model, input_method); + + wl_global_destroy(input_method->input_method_global); + wl_list_remove(&input_method->destroy_listener.link); + + free(input_method); +} + +static void +handle_keyboard_focus(struct wl_listener *listener, void *data) +{ + struct weston_keyboard *keyboard = data; + struct input_method *input_method = + container_of(listener, struct input_method, keyboard_focus_listener); + struct weston_surface *surface = keyboard->focus; + + if (!input_method->model) + return; + + if (!surface || input_method->model->surface != surface) + deactivate_text_input(input_method->model, + input_method); +} + +static void +input_method_init_seat(struct weston_seat *seat) +{ + if (seat->input_method->focus_listener_initialized) + return; + + if (seat->keyboard) { + seat->input_method->keyboard_focus_listener.notify = handle_keyboard_focus; + wl_signal_add(&seat->keyboard->focus_signal, &seat->input_method->keyboard_focus_listener); + seat->keyboard->input_method_grab.interface = &input_method_context_grab; + } + + seat->input_method->focus_listener_initialized = 1; +} + +static void launch_input_method(struct text_backend *text_backend); + +static void +handle_input_method_sigchld(struct weston_process *process, int status) +{ + uint32_t time; + struct text_backend *text_backend = + container_of(process, struct text_backend, input_method.process); + + text_backend->input_method.process.pid = 0; + text_backend->input_method.client = NULL; + + /* if input_method dies more than 5 times in 10 seconds, give up */ + time = weston_compositor_get_time(); + if (time - text_backend->input_method.deathstamp > 10000) { + text_backend->input_method.deathstamp = time; + text_backend->input_method.deathcount = 0; + } + + text_backend->input_method.deathcount++; + if (text_backend->input_method.deathcount > 5) { + weston_log("input_method died, giving up.\n"); + return; + } + + weston_log("input_method died, respawning...\n"); + launch_input_method(text_backend); +} + +static void +launch_input_method(struct text_backend *text_backend) +{ + if (text_backend->input_method.binding) + return; + + if (!text_backend->input_method.path) + return; + + if (text_backend->input_method.process.pid != 0) + return; + + text_backend->input_method.client = weston_client_launch(text_backend->compositor, + &text_backend->input_method.process, + text_backend->input_method.path, + handle_input_method_sigchld); + + if (!text_backend->input_method.client) + weston_log("not able to start %s\n", text_backend->input_method.path); +} + +static void +handle_seat_created(struct wl_listener *listener, + void *data) +{ + struct weston_seat *seat = data; + struct text_backend *text_backend = + container_of(listener, struct text_backend, + seat_created_listener); + struct input_method *input_method; + struct weston_compositor *ec = seat->compositor; + + input_method = calloc(1, sizeof *input_method); + + input_method->seat = seat; + input_method->model = NULL; + input_method->focus_listener_initialized = 0; + input_method->context = NULL; + input_method->text_backend = text_backend; + + input_method->input_method_global = + wl_global_create(ec->wl_display, &wl_input_method_interface, 1, + input_method, bind_input_method); + + input_method->destroy_listener.notify = input_method_notifier_destroy; + wl_signal_add(&seat->destroy_signal, &input_method->destroy_listener); + + seat->input_method = input_method; + + launch_input_method(text_backend); +} + +static void +text_backend_configuration(struct text_backend *text_backend) +{ + struct weston_config_section *section; + + section = weston_config_get_section(text_backend->compositor->config, + "input-method", NULL, NULL); + weston_config_section_get_string(section, "path", + &text_backend->input_method.path, + LIBEXECDIR "/weston-keyboard"); +} + +static void +text_backend_notifier_destroy(struct wl_listener *listener, void *data) +{ + struct text_backend *text_backend = + container_of(listener, struct text_backend, destroy_listener); + + if (text_backend->input_method.client) + wl_client_destroy(text_backend->input_method.client); + + free(text_backend->input_method.path); + + free(text_backend); +} + + +WL_EXPORT int +text_backend_init(struct weston_compositor *ec) +{ + struct text_backend *text_backend; + + text_backend = calloc(1, sizeof(*text_backend)); + + text_backend->compositor = ec; + + text_backend->seat_created_listener.notify = handle_seat_created; + wl_signal_add(&ec->seat_created_signal, + &text_backend->seat_created_listener); + + text_backend->destroy_listener.notify = text_backend_notifier_destroy; + wl_signal_add(&ec->destroy_signal, &text_backend->destroy_listener); + + text_backend_configuration(text_backend); + + text_input_manager_create(ec); + + return 0; +} diff --git a/src/udev-seat.c b/src/udev-seat.c new file mode 100644 index 00000000..ffaf08aa --- /dev/null +++ b/src/udev-seat.c @@ -0,0 +1,380 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "compositor.h" +#include "launcher-util.h" +#include "evdev.h" +#include "udev-seat.h" + +static const char default_seat[] = "seat0"; +static const char default_seat_name[] = "default"; + +static struct udev_seat * +udev_seat_create(struct weston_compositor *c, const char *seat_name); +static void +udev_seat_destroy(struct udev_seat *seat); + +static int +device_added(struct udev_device *udev_device, struct udev_input *input) +{ + struct weston_compositor *c; + struct evdev_device *device; + struct weston_output *output; + const char *devnode; + const char *device_seat, *seat_name, *output_name; + const char *calibration_values; + int fd; + struct udev_seat *seat; + + device_seat = udev_device_get_property_value(udev_device, "ID_SEAT"); + if (!device_seat) + device_seat = default_seat; + + if (strcmp(device_seat, input->seat_id)) + return 0; + + c = input->compositor; + devnode = udev_device_get_devnode(udev_device); + + /* Search for matching logical seat */ + seat_name = udev_device_get_property_value(udev_device, "WL_SEAT"); + if (!seat_name) + seat_name = default_seat_name; + + seat = udev_seat_get_named(c, seat_name); + + if (seat == NULL) + return -1; + + /* Use non-blocking mode so that we can loop on read on + * evdev_device_data() until all events on the fd are + * read. mtdev_get() also expects this. */ + fd = weston_launcher_open(c->launcher, devnode, O_RDWR | O_NONBLOCK); + if (fd < 0) { + weston_log("opening input device '%s' failed.\n", devnode); + return 0; + } + + device = evdev_device_create(&seat->base, devnode, fd); + if (device == EVDEV_UNHANDLED_DEVICE) { + close(fd); + weston_log("not using input device '%s'.\n", devnode); + return 0; + } else if (device == NULL) { + close(fd); + weston_log("failed to create input device '%s'.\n", devnode); + return 0; + } + + calibration_values = + udev_device_get_property_value(udev_device, + "WL_CALIBRATION"); + + if (calibration_values && sscanf(calibration_values, + "%f %f %f %f %f %f", + &device->abs.calibration[0], + &device->abs.calibration[1], + &device->abs.calibration[2], + &device->abs.calibration[3], + &device->abs.calibration[4], + &device->abs.calibration[5]) == 6) { + device->abs.apply_calibration = 1; + weston_log ("Applying calibration: %f %f %f %f %f %f\n", + device->abs.calibration[0], + device->abs.calibration[1], + device->abs.calibration[2], + device->abs.calibration[3], + device->abs.calibration[4], + device->abs.calibration[5]); + } + + wl_list_insert(seat->devices_list.prev, &device->link); + + if (seat->base.output && seat->base.pointer) + weston_pointer_clamp(seat->base.pointer, + &seat->base.pointer->x, + &seat->base.pointer->y); + + output_name = udev_device_get_property_value(udev_device, "WL_OUTPUT"); + if (output_name) { + wl_list_for_each(output, &c->output_list, link) + if (strcmp(output->name, output_name) == 0) + device->output = output; + } + + if (input->enabled == 1) + weston_seat_repick(&seat->base); + + return 0; +} + +static int +udev_input_add_devices(struct udev_input *input, struct udev *udev) +{ + struct udev_enumerate *e; + struct udev_list_entry *entry; + struct udev_device *device; + const char *path, *sysname; + struct udev_seat *seat; + int devices_found = 0; + + e = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(e, "input"); + udev_enumerate_scan_devices(e); + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { + path = udev_list_entry_get_name(entry); + device = udev_device_new_from_syspath(udev, path); + + sysname = udev_device_get_sysname(device); + if (strncmp("event", sysname, 5) != 0) { + udev_device_unref(device); + continue; + } + + if (device_added(device, input) < 0) { + udev_device_unref(device); + udev_enumerate_unref(e); + return -1; + } + + udev_device_unref(device); + } + udev_enumerate_unref(e); + + wl_list_for_each(seat, &input->compositor->seat_list, base.link) { + evdev_notify_keyboard_focus(&seat->base, &seat->devices_list); + + if (!wl_list_empty(&seat->devices_list)) + devices_found = 1; + } + + if (devices_found == 0) { + weston_log( + "warning: no input devices on entering Weston. " + "Possible causes:\n" + "\t- no permissions to read /dev/input/event*\n" + "\t- seats misconfigured " + "(Weston backend option 'seat', " + "udev device property ID_SEAT)\n"); + return -1; + } + + return 0; +} + +static int +evdev_udev_handler(int fd, uint32_t mask, void *data) +{ + struct udev_input *input = data; + struct udev_device *udev_device; + struct evdev_device *device, *next; + const char *action; + const char *devnode; + struct udev_seat *seat; + + udev_device = udev_monitor_receive_device(input->udev_monitor); + if (!udev_device) + return 1; + + action = udev_device_get_action(udev_device); + if (!action) + goto out; + + if (strncmp("event", udev_device_get_sysname(udev_device), 5) != 0) + goto out; + + if (!strcmp(action, "add")) { + device_added(udev_device, input); + } + else if (!strcmp(action, "remove")) { + devnode = udev_device_get_devnode(udev_device); + wl_list_for_each(seat, &input->compositor->seat_list, base.link) { + wl_list_for_each_safe(device, next, &seat->devices_list, link) + if (!strcmp(device->devnode, devnode)) { + weston_log("input device %s, %s removed\n", + device->devname, device->devnode); + evdev_device_destroy(device); + break; + } + } + } + +out: + udev_device_unref(udev_device); + + return 0; +} + +int +udev_input_enable(struct udev_input *input, struct udev *udev) +{ + struct wl_event_loop *loop; + struct weston_compositor *c = input->compositor; + int fd; + + input->udev_monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (!input->udev_monitor) { + weston_log("udev: failed to create the udev monitor\n"); + return -1; + } + + udev_monitor_filter_add_match_subsystem_devtype(input->udev_monitor, + "input", NULL); + + if (udev_monitor_enable_receiving(input->udev_monitor)) { + weston_log("udev: failed to bind the udev monitor\n"); + udev_monitor_unref(input->udev_monitor); + return -1; + } + + loop = wl_display_get_event_loop(c->wl_display); + fd = udev_monitor_get_fd(input->udev_monitor); + input->udev_monitor_source = + wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE, + evdev_udev_handler, input); + if (!input->udev_monitor_source) { + udev_monitor_unref(input->udev_monitor); + return -1; + } + + if (udev_input_add_devices(input, udev) < 0) + return -1; + + input->enabled = 1; + + return 0; +} + +static void +udev_input_remove_devices(struct udev_input *input) +{ + struct evdev_device *device, *next; + struct udev_seat *seat; + + wl_list_for_each(seat, &input->compositor->seat_list, base.link) { + wl_list_for_each_safe(device, next, &seat->devices_list, link) + evdev_device_destroy(device); + + if (seat->base.keyboard) + notify_keyboard_focus_out(&seat->base); + } +} + +void +udev_input_disable(struct udev_input *input) +{ + if (!input->udev_monitor) + return; + + udev_monitor_unref(input->udev_monitor); + input->udev_monitor = NULL; + wl_event_source_remove(input->udev_monitor_source); + input->udev_monitor_source = NULL; + + udev_input_remove_devices(input); +} + + +int +udev_input_init(struct udev_input *input, struct weston_compositor *c, struct udev *udev, + const char *seat_id) +{ + memset(input, 0, sizeof *input); + input->seat_id = strdup(seat_id); + input->compositor = c; + if (udev_input_enable(input, udev) < 0) + goto err; + + return 0; + + err: + free(input->seat_id); + return -1; +} + +void +udev_input_destroy(struct udev_input *input) +{ + struct udev_seat *seat, *next; + udev_input_disable(input); + wl_list_for_each_safe(seat, next, &input->compositor->seat_list, base.link) + udev_seat_destroy(seat); + free(input->seat_id); +} + +static void +drm_led_update(struct weston_seat *seat_base, enum weston_led leds) +{ + struct udev_seat *seat = (struct udev_seat *) seat_base; + struct evdev_device *device; + + wl_list_for_each(device, &seat->devices_list, link) + evdev_led_update(device, leds); +} + +static struct udev_seat * +udev_seat_create(struct weston_compositor *c, const char *seat_name) +{ + struct udev_seat *seat; + + seat = zalloc(sizeof *seat); + + if (!seat) + return NULL; + weston_seat_init(&seat->base, c, seat_name); + seat->base.led_update = drm_led_update; + + wl_list_init(&seat->devices_list); + return seat; +} + +static void +udev_seat_destroy(struct udev_seat *seat) +{ + weston_seat_release(&seat->base); + free(seat); +} + +struct udev_seat * +udev_seat_get_named(struct weston_compositor *c, const char *seat_name) +{ + struct udev_seat *seat; + + wl_list_for_each(seat, &c->seat_list, base.link) { + if (strcmp(seat->base.seat_name, seat_name) == 0) + return seat; + } + + seat = udev_seat_create(c, seat_name); + + if (!seat) + return NULL; + + return seat; +} diff --git a/src/udev-seat.h b/src/udev-seat.h new file mode 100644 index 00000000..4cb6f071 --- /dev/null +++ b/src/udev-seat.h @@ -0,0 +1,56 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _UDEV_SEAT_H_ +#define _UDEV_SEAT_H_ + +#include "config.h" + +#include + +#include "compositor.h" + +struct udev_seat { + struct weston_seat base; + struct wl_list devices_list; +}; + +struct udev_input { + struct udev_monitor *udev_monitor; + struct wl_event_source *udev_monitor_source; + char *seat_id; + struct weston_compositor *compositor; + int enabled; +}; + + +int udev_input_enable(struct udev_input *input, struct udev *udev); +void udev_input_disable(struct udev_input *input); +int udev_input_init(struct udev_input *input, + struct weston_compositor *c, + struct udev *udev, + const char *seat_id); +void udev_input_destroy(struct udev_input *input); + +struct udev_seat *udev_seat_get_named(struct weston_compositor *c, + const char *seat_name); +#endif diff --git a/src/vaapi-recorder.c b/src/vaapi-recorder.c new file mode 100644 index 00000000..41314071 --- /dev/null +++ b/src/vaapi-recorder.c @@ -0,0 +1,1154 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Copyright (c) 2012 Intel Corporation. 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, sub license, 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 NON-INFRINGEMENT. + * IN NO EVENT SHALL PRECISION INSIGHT 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. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "compositor.h" +#include "vaapi-recorder.h" + +#define NAL_REF_IDC_NONE 0 +#define NAL_REF_IDC_LOW 1 +#define NAL_REF_IDC_MEDIUM 2 +#define NAL_REF_IDC_HIGH 3 + +#define NAL_NON_IDR 1 +#define NAL_IDR 5 +#define NAL_SPS 7 +#define NAL_PPS 8 +#define NAL_SEI 6 + +#define SLICE_TYPE_P 0 +#define SLICE_TYPE_B 1 +#define SLICE_TYPE_I 2 + +#define ENTROPY_MODE_CAVLC 0 +#define ENTROPY_MODE_CABAC 1 + +#define PROFILE_IDC_BASELINE 66 +#define PROFILE_IDC_MAIN 77 +#define PROFILE_IDC_HIGH 100 + +struct vaapi_recorder { + int drm_fd, output_fd; + int width, height; + int frame_count; + + int destroying; + pthread_t worker_thread; + pthread_mutex_t mutex; + pthread_cond_t input_cond; + + struct { + int valid; + int prime_fd, stride; + } input; + + VADisplay va_dpy; + + /* video post processing is used for colorspace conversion */ + struct { + VAConfigID cfg; + VAContextID ctx; + VABufferID pipeline_buf; + VASurfaceID output; + } vpp; + + struct { + VAConfigID cfg; + VAContextID ctx; + VASurfaceID reference_picture[3]; + + int intra_period; + int output_size; + int constraint_set_flag; + + struct { + VAEncSequenceParameterBufferH264 seq; + VAEncPictureParameterBufferH264 pic; + VAEncSliceParameterBufferH264 slice; + } param; + } encoder; +}; + +static void * +worker_thread_function(void *); + +/* bistream code used for writing the packed headers */ + +#define BITSTREAM_ALLOCATE_STEPPING 4096 + +struct bitstream { + unsigned int *buffer; + int bit_offset; + int max_size_in_dword; +}; + +static unsigned int +va_swap32(unsigned int val) +{ + unsigned char *pval = (unsigned char *)&val; + + return ((pval[0] << 24) | + (pval[1] << 16) | + (pval[2] << 8) | + (pval[3] << 0)); +} + +static void +bitstream_start(struct bitstream *bs) +{ + bs->max_size_in_dword = BITSTREAM_ALLOCATE_STEPPING; + bs->buffer = calloc(bs->max_size_in_dword * sizeof(int), 1); + bs->bit_offset = 0; +} + +static void +bitstream_end(struct bitstream *bs) +{ + int pos = (bs->bit_offset >> 5); + int bit_offset = (bs->bit_offset & 0x1f); + int bit_left = 32 - bit_offset; + + if (bit_offset) { + bs->buffer[pos] = va_swap32((bs->buffer[pos] << bit_left)); + } +} + +static void +bitstream_put_ui(struct bitstream *bs, unsigned int val, int size_in_bits) +{ + int pos = (bs->bit_offset >> 5); + int bit_offset = (bs->bit_offset & 0x1f); + int bit_left = 32 - bit_offset; + + if (!size_in_bits) + return; + + bs->bit_offset += size_in_bits; + + if (bit_left > size_in_bits) { + bs->buffer[pos] = (bs->buffer[pos] << size_in_bits | val); + return; + } + + size_in_bits -= bit_left; + bs->buffer[pos] = + (bs->buffer[pos] << bit_left) | (val >> size_in_bits); + bs->buffer[pos] = va_swap32(bs->buffer[pos]); + + if (pos + 1 == bs->max_size_in_dword) { + bs->max_size_in_dword += BITSTREAM_ALLOCATE_STEPPING; + bs->buffer = + realloc(bs->buffer, + bs->max_size_in_dword * sizeof(unsigned int)); + } + + bs->buffer[pos + 1] = val; +} + +static void +bitstream_put_ue(struct bitstream *bs, unsigned int val) +{ + int size_in_bits = 0; + int tmp_val = ++val; + + while (tmp_val) { + tmp_val >>= 1; + size_in_bits++; + } + + bitstream_put_ui(bs, 0, size_in_bits - 1); // leading zero + bitstream_put_ui(bs, val, size_in_bits); +} + +static void +bitstream_put_se(struct bitstream *bs, int val) +{ + unsigned int new_val; + + if (val <= 0) + new_val = -2 * val; + else + new_val = 2 * val - 1; + + bitstream_put_ue(bs, new_val); +} + +static void +bitstream_byte_aligning(struct bitstream *bs, int bit) +{ + int bit_offset = (bs->bit_offset & 0x7); + int bit_left = 8 - bit_offset; + int new_val; + + if (!bit_offset) + return; + + if (bit) + new_val = (1 << bit_left) - 1; + else + new_val = 0; + + bitstream_put_ui(bs, new_val, bit_left); +} + +static VAStatus +encoder_create_config(struct vaapi_recorder *r) +{ + VAConfigAttrib attrib[2]; + VAStatus status; + + /* FIXME: should check if VAEntrypointEncSlice is supported */ + + /* FIXME: should check if specified attributes are supported */ + + attrib[0].type = VAConfigAttribRTFormat; + attrib[0].value = VA_RT_FORMAT_YUV420; + + attrib[1].type = VAConfigAttribRateControl; + attrib[1].value = VA_RC_CQP; + + status = vaCreateConfig(r->va_dpy, VAProfileH264Main, + VAEntrypointEncSlice, attrib, 2, + &r->encoder.cfg); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaCreateContext(r->va_dpy, r->encoder.cfg, + r->width, r->height, VA_PROGRESSIVE, 0, 0, + &r->encoder.ctx); + if (status != VA_STATUS_SUCCESS) { + vaDestroyConfig(r->va_dpy, r->encoder.cfg); + return status; + } + + return VA_STATUS_SUCCESS; +} + +static void +encoder_destroy_config(struct vaapi_recorder *r) +{ + vaDestroyContext(r->va_dpy, r->encoder.ctx); + vaDestroyConfig(r->va_dpy, r->encoder.cfg); +} + +static void +encoder_init_seq_parameters(struct vaapi_recorder *r) +{ + int width_in_mbs, height_in_mbs; + int frame_cropping_flag = 0; + int frame_crop_bottom_offset = 0; + + width_in_mbs = (r->width + 15) / 16; + height_in_mbs = (r->height + 15) / 16; + + r->encoder.param.seq.level_idc = 41; + r->encoder.param.seq.intra_period = r->encoder.intra_period; + r->encoder.param.seq.max_num_ref_frames = 4; + r->encoder.param.seq.picture_width_in_mbs = width_in_mbs; + r->encoder.param.seq.picture_height_in_mbs = height_in_mbs; + r->encoder.param.seq.seq_fields.bits.frame_mbs_only_flag = 1; + + /* Tc = num_units_in_tick / time_scale */ + r->encoder.param.seq.time_scale = 1800; + r->encoder.param.seq.num_units_in_tick = 15; + + if (height_in_mbs * 16 - r->height > 0) { + frame_cropping_flag = 1; + frame_crop_bottom_offset = (height_in_mbs * 16 - r->height) / 2; + } + + r->encoder.param.seq.frame_cropping_flag = frame_cropping_flag; + r->encoder.param.seq.frame_crop_bottom_offset = frame_crop_bottom_offset; + + r->encoder.param.seq.seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = 2; +} + +static VABufferID +encoder_update_seq_parameters(struct vaapi_recorder *r) +{ + VABufferID seq_buf; + VAStatus status; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncSequenceParameterBufferType, + sizeof(r->encoder.param.seq), + 1, &r->encoder.param.seq, + &seq_buf); + + if (status == VA_STATUS_SUCCESS) + return seq_buf; + else + return VA_INVALID_ID; +} + +static void +encoder_init_pic_parameters(struct vaapi_recorder *r) +{ + VAEncPictureParameterBufferH264 *pic = &r->encoder.param.pic; + + pic->pic_init_qp = 0; + + /* ENTROPY_MODE_CABAC */ + pic->pic_fields.bits.entropy_coding_mode_flag = 1; + + pic->pic_fields.bits.deblocking_filter_control_present_flag = 1; +} + +static VABufferID +encoder_update_pic_parameters(struct vaapi_recorder *r, + VABufferID output_buf) +{ + VAEncPictureParameterBufferH264 *pic = &r->encoder.param.pic; + VAStatus status; + VABufferID pic_param_buf; + VASurfaceID curr_pic, pic0; + + curr_pic = r->encoder.reference_picture[r->frame_count % 2]; + pic0 = r->encoder.reference_picture[(r->frame_count + 1) % 2]; + + pic->CurrPic.picture_id = curr_pic; + pic->CurrPic.TopFieldOrderCnt = r->frame_count * 2; + pic->ReferenceFrames[0].picture_id = pic0; + pic->ReferenceFrames[1].picture_id = r->encoder.reference_picture[2]; + pic->ReferenceFrames[2].picture_id = VA_INVALID_ID; + + pic->coded_buf = output_buf; + pic->frame_num = r->frame_count; + + pic->pic_fields.bits.idr_pic_flag = (r->frame_count == 0); + pic->pic_fields.bits.reference_pic_flag = 1; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncPictureParameterBufferType, + sizeof(VAEncPictureParameterBufferH264), 1, + pic, &pic_param_buf); + + if (status == VA_STATUS_SUCCESS) + return pic_param_buf; + else + return VA_INVALID_ID; +} + +static VABufferID +encoder_update_slice_parameter(struct vaapi_recorder *r, int slice_type) +{ + VABufferID slice_param_buf; + VAStatus status; + + int width_in_mbs = (r->width + 15) / 16; + int height_in_mbs = (r->height + 15) / 16; + + memset(&r->encoder.param.slice, 0, sizeof r->encoder.param.slice); + + r->encoder.param.slice.num_macroblocks = width_in_mbs * height_in_mbs; + r->encoder.param.slice.slice_type = slice_type; + + r->encoder.param.slice.slice_alpha_c0_offset_div2 = 2; + r->encoder.param.slice.slice_beta_offset_div2 = 2; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncSliceParameterBufferType, + sizeof(r->encoder.param.slice), 1, + &r->encoder.param.slice, + &slice_param_buf); + + if (status == VA_STATUS_SUCCESS) + return slice_param_buf; + else + return VA_INVALID_ID; +} + +static VABufferID +encoder_update_misc_hdr_parameter(struct vaapi_recorder *r) +{ + VAEncMiscParameterBuffer *misc_param; + VAEncMiscParameterHRD *hrd; + VABufferID buffer; + VAStatus status; + + int total_size = + sizeof(VAEncMiscParameterBuffer) + + sizeof(VAEncMiscParameterRateControl); + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncMiscParameterBufferType, total_size, + 1, NULL, &buffer); + if (status != VA_STATUS_SUCCESS) + return VA_INVALID_ID; + + status = vaMapBuffer(r->va_dpy, buffer, (void **) &misc_param); + if (status != VA_STATUS_SUCCESS) { + vaDestroyBuffer(r->va_dpy, buffer); + return VA_INVALID_ID; + } + + misc_param->type = VAEncMiscParameterTypeHRD; + hrd = (VAEncMiscParameterHRD *) misc_param->data; + + hrd->initial_buffer_fullness = 0; + hrd->buffer_size = 0; + + vaUnmapBuffer(r->va_dpy, buffer); + + return buffer; +} + +static int +setup_encoder(struct vaapi_recorder *r) +{ + VAStatus status; + + status = encoder_create_config(r); + if (status != VA_STATUS_SUCCESS) { + return -1; + } + + status = vaCreateSurfaces(r->va_dpy, VA_RT_FORMAT_YUV420, + r->width, r->height, + r->encoder.reference_picture, 3, + NULL, 0); + if (status != VA_STATUS_SUCCESS) { + encoder_destroy_config(r); + return -1; + } + + /* VAProfileH264Main */ + r->encoder.constraint_set_flag |= (1 << 1); /* Annex A.2.2 */ + + r->encoder.output_size = r->width * r->height; + + r->encoder.intra_period = 30; + + encoder_init_seq_parameters(r); + encoder_init_pic_parameters(r); + + return 0; +} + +static void +encoder_destroy(struct vaapi_recorder *r) +{ + vaDestroySurfaces(r->va_dpy, r->encoder.reference_picture, 3); + + encoder_destroy_config(r); +} + +static void +nal_start_code_prefix(struct bitstream *bs) +{ + bitstream_put_ui(bs, 0x00000001, 32); +} + +static void +nal_header(struct bitstream *bs, int nal_ref_idc, int nal_unit_type) +{ + /* forbidden_zero_bit: 0 */ + bitstream_put_ui(bs, 0, 1); + + bitstream_put_ui(bs, nal_ref_idc, 2); + bitstream_put_ui(bs, nal_unit_type, 5); +} + +static void +rbsp_trailing_bits(struct bitstream *bs) +{ + bitstream_put_ui(bs, 1, 1); + bitstream_byte_aligning(bs, 0); +} + +static void sps_rbsp(struct bitstream *bs, + VAEncSequenceParameterBufferH264 *seq, + int constraint_set_flag) +{ + int i; + + bitstream_put_ui(bs, PROFILE_IDC_MAIN, 8); + + /* constraint_set[0-3] flag */ + for (i = 0; i < 4; i++) { + int set = (constraint_set_flag & (1 << i)) ? 1 : 0; + bitstream_put_ui(bs, set, 1); + } + + /* reserved_zero_4bits */ + bitstream_put_ui(bs, 0, 4); + bitstream_put_ui(bs, seq->level_idc, 8); + bitstream_put_ue(bs, seq->seq_parameter_set_id); + + bitstream_put_ue(bs, seq->seq_fields.bits.log2_max_frame_num_minus4); + bitstream_put_ue(bs, seq->seq_fields.bits.pic_order_cnt_type); + bitstream_put_ue(bs, + seq->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4); + + bitstream_put_ue(bs, seq->max_num_ref_frames); + + /* gaps_in_frame_num_value_allowed_flag */ + bitstream_put_ui(bs, 0, 1); + + /* pic_width_in_mbs_minus1, pic_height_in_map_units_minus1 */ + bitstream_put_ue(bs, seq->picture_width_in_mbs - 1); + bitstream_put_ue(bs, seq->picture_height_in_mbs - 1); + + bitstream_put_ui(bs, seq->seq_fields.bits.frame_mbs_only_flag, 1); + bitstream_put_ui(bs, seq->seq_fields.bits.direct_8x8_inference_flag, 1); + + bitstream_put_ui(bs, seq->frame_cropping_flag, 1); + + if (seq->frame_cropping_flag) { + bitstream_put_ue(bs, seq->frame_crop_left_offset); + bitstream_put_ue(bs, seq->frame_crop_right_offset); + bitstream_put_ue(bs, seq->frame_crop_top_offset); + bitstream_put_ue(bs, seq->frame_crop_bottom_offset); + } + + /* vui_parameters_present_flag */ + bitstream_put_ui(bs, 1, 1); + + /* aspect_ratio_info_present_flag */ + bitstream_put_ui(bs, 0, 1); + /* overscan_info_present_flag */ + bitstream_put_ui(bs, 0, 1); + + /* video_signal_type_present_flag */ + bitstream_put_ui(bs, 0, 1); + /* chroma_loc_info_present_flag */ + bitstream_put_ui(bs, 0, 1); + + /* timing_info_present_flag */ + bitstream_put_ui(bs, 1, 1); + bitstream_put_ui(bs, seq->num_units_in_tick, 32); + bitstream_put_ui(bs, seq->time_scale, 32); + /* fixed_frame_rate_flag */ + bitstream_put_ui(bs, 1, 1); + + /* nal_hrd_parameters_present_flag */ + bitstream_put_ui(bs, 0, 1); + + /* vcl_hrd_parameters_present_flag */ + bitstream_put_ui(bs, 0, 1); + + /* low_delay_hrd_flag */ + bitstream_put_ui(bs, 0, 1); + + /* pic_struct_present_flag */ + bitstream_put_ui(bs, 0, 1); + /* bitstream_restriction_flag */ + bitstream_put_ui(bs, 0, 1); + + rbsp_trailing_bits(bs); +} + +static void pps_rbsp(struct bitstream *bs, + VAEncPictureParameterBufferH264 *pic) +{ + /* pic_parameter_set_id, seq_parameter_set_id */ + bitstream_put_ue(bs, pic->pic_parameter_set_id); + bitstream_put_ue(bs, pic->seq_parameter_set_id); + + bitstream_put_ui(bs, pic->pic_fields.bits.entropy_coding_mode_flag, 1); + + /* pic_order_present_flag: 0 */ + bitstream_put_ui(bs, 0, 1); + + /* num_slice_groups_minus1 */ + bitstream_put_ue(bs, 0); + + bitstream_put_ue(bs, pic->num_ref_idx_l0_active_minus1); + bitstream_put_ue(bs, pic->num_ref_idx_l1_active_minus1); + + bitstream_put_ui(bs, pic->pic_fields.bits.weighted_pred_flag, 1); + bitstream_put_ui(bs, pic->pic_fields.bits.weighted_bipred_idc, 2); + + /* pic_init_qp_minus26, pic_init_qs_minus26, chroma_qp_index_offset */ + bitstream_put_se(bs, pic->pic_init_qp - 26); + bitstream_put_se(bs, 0); + bitstream_put_se(bs, 0); + + bitstream_put_ui(bs, pic->pic_fields.bits.deblocking_filter_control_present_flag, 1); + + /* constrained_intra_pred_flag, redundant_pic_cnt_present_flag */ + bitstream_put_ui(bs, 0, 1); + bitstream_put_ui(bs, 0, 1); + + bitstream_put_ui(bs, pic->pic_fields.bits.transform_8x8_mode_flag, 1); + + /* pic_scaling_matrix_present_flag */ + bitstream_put_ui(bs, 0, 1); + bitstream_put_se(bs, pic->second_chroma_qp_index_offset ); + + rbsp_trailing_bits(bs); +} + +static int +build_packed_pic_buffer(struct vaapi_recorder *r, + void **header_buffer) +{ + struct bitstream bs; + + bitstream_start(&bs); + nal_start_code_prefix(&bs); + nal_header(&bs, NAL_REF_IDC_HIGH, NAL_PPS); + pps_rbsp(&bs, &r->encoder.param.pic); + bitstream_end(&bs); + + *header_buffer = bs.buffer; + return bs.bit_offset; +} + +static int +build_packed_seq_buffer(struct vaapi_recorder *r, + void **header_buffer) +{ + struct bitstream bs; + + bitstream_start(&bs); + nal_start_code_prefix(&bs); + nal_header(&bs, NAL_REF_IDC_HIGH, NAL_SPS); + sps_rbsp(&bs, &r->encoder.param.seq, r->encoder.constraint_set_flag); + bitstream_end(&bs); + + *header_buffer = bs.buffer; + return bs.bit_offset; +} + +static int +create_packed_header_buffers(struct vaapi_recorder *r, VABufferID *buffers, + VAEncPackedHeaderType type, + void *data, int bit_length) +{ + VAEncPackedHeaderParameterBuffer packed_header; + VAStatus status; + + packed_header.type = type; + packed_header.bit_length = bit_length; + packed_header.has_emulation_bytes = 0; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncPackedHeaderParameterBufferType, + sizeof packed_header, 1, &packed_header, + &buffers[0]); + if (status != VA_STATUS_SUCCESS) + return 0; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncPackedHeaderDataBufferType, + (bit_length + 7) / 8, 1, data, &buffers[1]); + if (status != VA_STATUS_SUCCESS) { + vaDestroyBuffer(r->va_dpy, buffers[0]); + return 0; + } + + return 2; +} + +static int +encoder_prepare_headers(struct vaapi_recorder *r, VABufferID *buffers) +{ + VABufferID *p; + + int bit_length; + void *data; + + p = buffers; + + bit_length = build_packed_seq_buffer(r, &data); + p += create_packed_header_buffers(r, p, VAEncPackedHeaderSequence, + data, bit_length); + free(data); + + bit_length = build_packed_pic_buffer(r, &data); + p += create_packed_header_buffers(r, p, VAEncPackedHeaderPicture, + data, bit_length); + free(data); + + return p - buffers; +} + +static VAStatus +encoder_render_picture(struct vaapi_recorder *r, VASurfaceID input, + VABufferID *buffers, int count) +{ + VAStatus status; + + status = vaBeginPicture(r->va_dpy, r->encoder.ctx, input); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaRenderPicture(r->va_dpy, r->encoder.ctx, buffers, count); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaEndPicture(r->va_dpy, r->encoder.ctx); + if (status != VA_STATUS_SUCCESS) + return status; + + return vaSyncSurface(r->va_dpy, input); +} + +static VABufferID +encoder_create_output_buffer(struct vaapi_recorder *r) +{ + VABufferID output_buf; + VAStatus status; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncCodedBufferType, r->encoder.output_size, + 1, NULL, &output_buf); + if (status == VA_STATUS_SUCCESS) + return output_buf; + else + return VA_INVALID_ID; +} + +static int +encoder_write_output(struct vaapi_recorder *r, VABufferID output_buf) +{ + VACodedBufferSegment *segment; + VAStatus status; + int count; + + status = vaMapBuffer(r->va_dpy, output_buf, (void **) &segment); + if (status != VA_STATUS_SUCCESS) + return -1; + + if (segment->status & VA_CODED_BUF_STATUS_SLICE_OVERFLOW_MASK) { + r->encoder.output_size *= 2; + vaUnmapBuffer(r->va_dpy, output_buf); + return -1; + } + + count = write(r->output_fd, segment->buf, segment->size); + + vaUnmapBuffer(r->va_dpy, output_buf); + + return count; +} + +static void +encoder_encode(struct vaapi_recorder *r, VASurfaceID input) +{ + VABufferID output_buf = VA_INVALID_ID; + + VABufferID buffers[8]; + int count = 0; + + int slice_type; + int ret, i; + + if ((r->frame_count % r->encoder.intra_period) == 0) + slice_type = SLICE_TYPE_I; + else + slice_type = SLICE_TYPE_P; + + buffers[count++] = encoder_update_seq_parameters(r); + buffers[count++] = encoder_update_misc_hdr_parameter(r); + buffers[count++] = encoder_update_slice_parameter(r, slice_type); + + for (i = 0; i < count; i++) + if (buffers[i] == VA_INVALID_ID) + goto bail; + + if (r->frame_count == 0) + count += encoder_prepare_headers(r, buffers + count); + + do { + output_buf = encoder_create_output_buffer(r); + if (output_buf == VA_INVALID_ID) + goto bail; + + buffers[count++] = + encoder_update_pic_parameters(r, output_buf); + if (buffers[count - 1] == VA_INVALID_ID) + goto bail; + + encoder_render_picture(r, input, buffers, count); + ret = encoder_write_output(r, output_buf); + + vaDestroyBuffer(r->va_dpy, output_buf); + output_buf = VA_INVALID_ID; + + vaDestroyBuffer(r->va_dpy, buffers[--count]); + } while (ret < 0); + + for (i = 0; i < count; i++) + vaDestroyBuffer(r->va_dpy, buffers[i]); + + r->frame_count++; + return; + +bail: + for (i = 0; i < count; i++) + vaDestroyBuffer(r->va_dpy, buffers[i]); + if (output_buf != VA_INVALID_ID) + vaDestroyBuffer(r->va_dpy, output_buf); +} + + +static int +setup_vpp(struct vaapi_recorder *r) +{ + VAStatus status; + + status = vaCreateConfig(r->va_dpy, VAProfileNone, + VAEntrypointVideoProc, NULL, 0, + &r->vpp.cfg); + if (status != VA_STATUS_SUCCESS) { + weston_log("vaapi: failed to create VPP config\n"); + return -1; + } + + status = vaCreateContext(r->va_dpy, r->vpp.cfg, r->width, r->height, + 0, NULL, 0, &r->vpp.ctx); + if (status != VA_STATUS_SUCCESS) { + weston_log("vaapi: failed to create VPP context\n"); + goto err_cfg; + } + + status = vaCreateBuffer(r->va_dpy, r->vpp.ctx, + VAProcPipelineParameterBufferType, + sizeof(VAProcPipelineParameterBuffer), + 1, NULL, &r->vpp.pipeline_buf); + if (status != VA_STATUS_SUCCESS) { + weston_log("vaapi: failed to create VPP pipeline buffer\n"); + goto err_ctx; + } + + status = vaCreateSurfaces(r->va_dpy, VA_RT_FORMAT_YUV420, + r->width, r->height, &r->vpp.output, 1, + NULL, 0); + if (status != VA_STATUS_SUCCESS) { + weston_log("vaapi: failed to create YUV surface\n"); + goto err_buf; + } + + return 0; + +err_buf: + vaDestroyBuffer(r->va_dpy, r->vpp.pipeline_buf); +err_ctx: + vaDestroyConfig(r->va_dpy, r->vpp.ctx); +err_cfg: + vaDestroyConfig(r->va_dpy, r->vpp.cfg); + + return -1; +} + +static void +vpp_destroy(struct vaapi_recorder *r) +{ + vaDestroySurfaces(r->va_dpy, &r->vpp.output, 1); + vaDestroyBuffer(r->va_dpy, r->vpp.pipeline_buf); + vaDestroyConfig(r->va_dpy, r->vpp.ctx); + vaDestroyConfig(r->va_dpy, r->vpp.cfg); +} + +static int +setup_worker_thread(struct vaapi_recorder *r) +{ + pthread_mutex_init(&r->mutex, NULL); + pthread_cond_init(&r->input_cond, NULL); + pthread_create(&r->worker_thread, NULL, worker_thread_function, r); + + return 1; +} + +static void +destroy_worker_thread(struct vaapi_recorder *r) +{ + pthread_mutex_lock(&r->mutex); + + /* Make sure the worker thread finishes */ + r->destroying = 1; + pthread_cond_signal(&r->input_cond); + + pthread_mutex_unlock(&r->mutex); + + pthread_join(r->worker_thread, NULL); + + pthread_mutex_destroy(&r->mutex); + pthread_cond_destroy(&r->input_cond); +} + +struct vaapi_recorder * +vaapi_recorder_create(int drm_fd, int width, int height, const char *filename) +{ + struct vaapi_recorder *r; + VAStatus status; + int major, minor; + int flags; + + r = calloc(1, sizeof *r); + if (!r) + return NULL; + + r->width = width; + r->height = height; + r->drm_fd = drm_fd; + + if (setup_worker_thread(r) < 0) + goto err_free; + + flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC; + r->output_fd = open(filename, flags, 0644); + if (r->output_fd < 0) + goto err_thread; + + r->va_dpy = vaGetDisplayDRM(drm_fd); + if (!r->va_dpy) { + weston_log("failed to create VA display\n"); + goto err_fd; + } + + status = vaInitialize(r->va_dpy, &major, &minor); + if (status != VA_STATUS_SUCCESS) { + weston_log("vaapi: failed to initialize display\n"); + goto err_fd; + } + + if (setup_vpp(r) < 0) { + weston_log("vaapi: failed to initialize VPP pipeline\n"); + goto err_va_dpy; + } + + if (setup_encoder(r) < 0) { + goto err_vpp; + } + + return r; + +err_vpp: + vpp_destroy(r); +err_va_dpy: + vaTerminate(r->va_dpy); +err_fd: + close(r->output_fd); +err_thread: + destroy_worker_thread(r); +err_free: + free(r); + + return NULL; +} + +void +vaapi_recorder_destroy(struct vaapi_recorder *r) +{ + destroy_worker_thread(r); + + encoder_destroy(r); + vpp_destroy(r); + + vaTerminate(r->va_dpy); + + close(r->output_fd); + close(r->drm_fd); + + free(r); +} + +static VAStatus +create_surface_from_fd(struct vaapi_recorder *r, int prime_fd, + int stride, VASurfaceID *surface) +{ + VASurfaceAttrib va_attribs[2]; + VASurfaceAttribExternalBuffers va_attrib_extbuf; + VAStatus status; + + unsigned long buffer_fd = prime_fd; + + va_attrib_extbuf.pixel_format = VA_FOURCC_BGRX; + va_attrib_extbuf.width = r->width; + va_attrib_extbuf.height = r->height; + va_attrib_extbuf.data_size = r->height * stride; + va_attrib_extbuf.num_planes = 1; + va_attrib_extbuf.pitches[0] = stride; + va_attrib_extbuf.offsets[0] = 0; + va_attrib_extbuf.buffers = &buffer_fd; + va_attrib_extbuf.num_buffers = 1; + va_attrib_extbuf.flags = 0; + va_attrib_extbuf.private_data = NULL; + + va_attribs[0].type = VASurfaceAttribMemoryType; + va_attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE; + va_attribs[0].value.type = VAGenericValueTypeInteger; + va_attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME; + + va_attribs[1].type = VASurfaceAttribExternalBufferDescriptor; + va_attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE; + va_attribs[1].value.type = VAGenericValueTypePointer; + va_attribs[1].value.value.p = &va_attrib_extbuf; + + status = vaCreateSurfaces(r->va_dpy, VA_RT_FORMAT_RGB32, + r->width, r->height, surface, 1, + va_attribs, 2); + + return status; +} + +static VAStatus +convert_rgb_to_yuv(struct vaapi_recorder *r, VASurfaceID rgb_surface) +{ + VAProcPipelineParameterBuffer *pipeline_param; + VAStatus status; + + status = vaMapBuffer(r->va_dpy, r->vpp.pipeline_buf, + (void **) &pipeline_param); + if (status != VA_STATUS_SUCCESS) + return status; + + memset(pipeline_param, 0, sizeof *pipeline_param); + + pipeline_param->surface = rgb_surface; + pipeline_param->surface_color_standard = VAProcColorStandardNone; + + pipeline_param->output_background_color = 0xff000000; + pipeline_param->output_color_standard = VAProcColorStandardNone; + + status = vaUnmapBuffer(r->va_dpy, r->vpp.pipeline_buf); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaBeginPicture(r->va_dpy, r->vpp.ctx, r->vpp.output); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaRenderPicture(r->va_dpy, r->vpp.ctx, + &r->vpp.pipeline_buf, 1); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaEndPicture(r->va_dpy, r->vpp.ctx); + if (status != VA_STATUS_SUCCESS) + return status; + + return status; +} + +static void +recorder_frame(struct vaapi_recorder *r) +{ + VASurfaceID rgb_surface; + VAStatus status; + + status = create_surface_from_fd(r, r->input.prime_fd, + r->input.stride, &rgb_surface); + if (status != VA_STATUS_SUCCESS) { + weston_log("[libva recorder] " + "failed to create surface from bo\n"); + return; + } + + close(r->input.prime_fd); + + status = convert_rgb_to_yuv(r, rgb_surface); + if (status != VA_STATUS_SUCCESS) { + weston_log("[libva recorder] " + "color space conversion failed\n"); + return; + } + + encoder_encode(r, r->vpp.output); + + vaDestroySurfaces(r->va_dpy, &rgb_surface, 1); +} + +static void * +worker_thread_function(void *data) +{ + struct vaapi_recorder *r = data; + + pthread_mutex_lock(&r->mutex); + + while (!r->destroying) { + if (!r->input.valid) + pthread_cond_wait(&r->input_cond, &r->mutex); + + /* If the thread is awaken by destroy_worker_thread(), + * there might not be valid input */ + if (!r->input.valid) + continue; + + recorder_frame(r); + r->input.valid = 0; + } + + pthread_mutex_unlock(&r->mutex); + + return NULL; +} + +void +vaapi_recorder_frame(struct vaapi_recorder *r, int prime_fd, int stride) +{ + pthread_mutex_lock(&r->mutex); + + /* The mutex is never released while encoding, so this point should + * never be reached if input.valid is true. */ + assert(!r->input.valid); + + r->input.prime_fd = prime_fd; + r->input.stride = stride; + r->input.valid = 1; + pthread_cond_signal(&r->input_cond); + + pthread_mutex_unlock(&r->mutex); +} diff --git a/src/vaapi-recorder.h b/src/vaapi-recorder.h new file mode 100644 index 00000000..664b1f96 --- /dev/null +++ b/src/vaapi-recorder.h @@ -0,0 +1,35 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _VAAPI_RECORDER_H_ +#define _VAAPI_RECORDER_H_ + +struct vaapi_recorder; + +struct vaapi_recorder * +vaapi_recorder_create(int drm_fd, int width, int height, const char *filename); +void +vaapi_recorder_destroy(struct vaapi_recorder *r); +void +vaapi_recorder_frame(struct vaapi_recorder *r, int fd, int stride); + +#endif /* _VAAPI_RECORDER_H_ */ diff --git a/src/version.h.in b/src/version.h.in new file mode 100644 index 00000000..79dba45c --- /dev/null +++ b/src/version.h.in @@ -0,0 +1,37 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef WESTON_VERSION_H +#define WESTON_VERSION_H + +#define WESTON_VERSION_MAJOR @WESTON_VERSION_MAJOR@ +#define WESTON_VERSION_MINOR @WESTON_VERSION_MINOR@ +#define WESTON_VERSION_MICRO @WESTON_VERSION_MICRO@ +#define WESTON_VERSION "@WESTON_VERSION@" + +/* Can be used like #if WESTON_VERSION_AT_LEAST(1, 2, 0) */ +#define WESTON_VERSION_AT_LEAST(major, minor, micro) \ + (WESTON_VERSION_MAJOR == (major) && \ + WESTON_VERSION_MINOR == (minor) && \ + WESTON_VERSION_MICRO >= (micro)) + +#endif diff --git a/src/vertex-clipping.c b/src/vertex-clipping.c new file mode 100644 index 00000000..603ce6f4 --- /dev/null +++ b/src/vertex-clipping.c @@ -0,0 +1,317 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include + +#include + +#include "vertex-clipping.h" + +GLfloat +float_difference(GLfloat a, GLfloat b) +{ + /* http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/ */ + static const GLfloat max_diff = 4.0f * FLT_MIN; + static const GLfloat max_rel_diff = 4.0e-5; + GLfloat diff = a - b; + GLfloat adiff = fabsf(diff); + + if (adiff <= max_diff) + return 0.0f; + + a = fabsf(a); + b = fabsf(b); + if (adiff <= (a > b ? a : b) * max_rel_diff) + return 0.0f; + + return diff; +} + +/* A line segment (p1x, p1y)-(p2x, p2y) intersects the line x = x_arg. + * Compute the y coordinate of the intersection. + */ +static GLfloat +clip_intersect_y(GLfloat p1x, GLfloat p1y, GLfloat p2x, GLfloat p2y, + GLfloat x_arg) +{ + GLfloat a; + GLfloat diff = float_difference(p1x, p2x); + + /* Practically vertical line segment, yet the end points have already + * been determined to be on different sides of the line. Therefore + * the line segment is part of the line and intersects everywhere. + * Return the end point, so we use the whole line segment. + */ + if (diff == 0.0f) + return p2y; + + a = (x_arg - p2x) / diff; + return p2y + (p1y - p2y) * a; +} + +/* A line segment (p1x, p1y)-(p2x, p2y) intersects the line y = y_arg. + * Compute the x coordinate of the intersection. + */ +static GLfloat +clip_intersect_x(GLfloat p1x, GLfloat p1y, GLfloat p2x, GLfloat p2y, + GLfloat y_arg) +{ + GLfloat a; + GLfloat diff = float_difference(p1y, p2y); + + /* Practically horizontal line segment, yet the end points have already + * been determined to be on different sides of the line. Therefore + * the line segment is part of the line and intersects everywhere. + * Return the end point, so we use the whole line segment. + */ + if (diff == 0.0f) + return p2x; + + a = (y_arg - p2y) / diff; + return p2x + (p1x - p2x) * a; +} + +enum path_transition { + PATH_TRANSITION_OUT_TO_OUT = 0, + PATH_TRANSITION_OUT_TO_IN = 1, + PATH_TRANSITION_IN_TO_OUT = 2, + PATH_TRANSITION_IN_TO_IN = 3, +}; + +static void +clip_append_vertex(struct clip_context *ctx, GLfloat x, GLfloat y) +{ + *ctx->vertices.x++ = x; + *ctx->vertices.y++ = y; +} + +static enum path_transition +path_transition_left_edge(struct clip_context *ctx, GLfloat x, GLfloat y) +{ + return ((ctx->prev.x >= ctx->clip.x1) << 1) | (x >= ctx->clip.x1); +} + +static enum path_transition +path_transition_right_edge(struct clip_context *ctx, GLfloat x, GLfloat y) +{ + return ((ctx->prev.x < ctx->clip.x2) << 1) | (x < ctx->clip.x2); +} + +static enum path_transition +path_transition_top_edge(struct clip_context *ctx, GLfloat x, GLfloat y) +{ + return ((ctx->prev.y >= ctx->clip.y1) << 1) | (y >= ctx->clip.y1); +} + +static enum path_transition +path_transition_bottom_edge(struct clip_context *ctx, GLfloat x, GLfloat y) +{ + return ((ctx->prev.y < ctx->clip.y2) << 1) | (y < ctx->clip.y2); +} + +static void +clip_polygon_leftright(struct clip_context *ctx, + enum path_transition transition, + GLfloat x, GLfloat y, GLfloat clip_x) +{ + GLfloat yi; + + switch (transition) { + case PATH_TRANSITION_IN_TO_IN: + clip_append_vertex(ctx, x, y); + break; + case PATH_TRANSITION_IN_TO_OUT: + yi = clip_intersect_y(ctx->prev.x, ctx->prev.y, x, y, clip_x); + clip_append_vertex(ctx, clip_x, yi); + break; + case PATH_TRANSITION_OUT_TO_IN: + yi = clip_intersect_y(ctx->prev.x, ctx->prev.y, x, y, clip_x); + clip_append_vertex(ctx, clip_x, yi); + clip_append_vertex(ctx, x, y); + break; + case PATH_TRANSITION_OUT_TO_OUT: + /* nothing */ + break; + default: + assert(0 && "bad enum path_transition"); + } + + ctx->prev.x = x; + ctx->prev.y = y; +} + +static void +clip_polygon_topbottom(struct clip_context *ctx, + enum path_transition transition, + GLfloat x, GLfloat y, GLfloat clip_y) +{ + GLfloat xi; + + switch (transition) { + case PATH_TRANSITION_IN_TO_IN: + clip_append_vertex(ctx, x, y); + break; + case PATH_TRANSITION_IN_TO_OUT: + xi = clip_intersect_x(ctx->prev.x, ctx->prev.y, x, y, clip_y); + clip_append_vertex(ctx, xi, clip_y); + break; + case PATH_TRANSITION_OUT_TO_IN: + xi = clip_intersect_x(ctx->prev.x, ctx->prev.y, x, y, clip_y); + clip_append_vertex(ctx, xi, clip_y); + clip_append_vertex(ctx, x, y); + break; + case PATH_TRANSITION_OUT_TO_OUT: + /* nothing */ + break; + default: + assert(0 && "bad enum path_transition"); + } + + ctx->prev.x = x; + ctx->prev.y = y; +} + +static void +clip_context_prepare(struct clip_context *ctx, const struct polygon8 *src, + GLfloat *dst_x, GLfloat *dst_y) +{ + ctx->prev.x = src->x[src->n - 1]; + ctx->prev.y = src->y[src->n - 1]; + ctx->vertices.x = dst_x; + ctx->vertices.y = dst_y; +} + +static int +clip_polygon_left(struct clip_context *ctx, const struct polygon8 *src, + GLfloat *dst_x, GLfloat *dst_y) +{ + enum path_transition trans; + int i; + + clip_context_prepare(ctx, src, dst_x, dst_y); + for (i = 0; i < src->n; i++) { + trans = path_transition_left_edge(ctx, src->x[i], src->y[i]); + clip_polygon_leftright(ctx, trans, src->x[i], src->y[i], + ctx->clip.x1); + } + return ctx->vertices.x - dst_x; +} + +static int +clip_polygon_right(struct clip_context *ctx, const struct polygon8 *src, + GLfloat *dst_x, GLfloat *dst_y) +{ + enum path_transition trans; + int i; + + clip_context_prepare(ctx, src, dst_x, dst_y); + for (i = 0; i < src->n; i++) { + trans = path_transition_right_edge(ctx, src->x[i], src->y[i]); + clip_polygon_leftright(ctx, trans, src->x[i], src->y[i], + ctx->clip.x2); + } + return ctx->vertices.x - dst_x; +} + +static int +clip_polygon_top(struct clip_context *ctx, const struct polygon8 *src, + GLfloat *dst_x, GLfloat *dst_y) +{ + enum path_transition trans; + int i; + + clip_context_prepare(ctx, src, dst_x, dst_y); + for (i = 0; i < src->n; i++) { + trans = path_transition_top_edge(ctx, src->x[i], src->y[i]); + clip_polygon_topbottom(ctx, trans, src->x[i], src->y[i], + ctx->clip.y1); + } + return ctx->vertices.x - dst_x; +} + +static int +clip_polygon_bottom(struct clip_context *ctx, const struct polygon8 *src, + GLfloat *dst_x, GLfloat *dst_y) +{ + enum path_transition trans; + int i; + + clip_context_prepare(ctx, src, dst_x, dst_y); + for (i = 0; i < src->n; i++) { + trans = path_transition_bottom_edge(ctx, src->x[i], src->y[i]); + clip_polygon_topbottom(ctx, trans, src->x[i], src->y[i], + ctx->clip.y2); + } + return ctx->vertices.x - dst_x; +} + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) > (b)) ? (b) : (a)) +#define clip(x, a, b) min(max(x, a), b) + +int +clip_simple(struct clip_context *ctx, + struct polygon8 *surf, + GLfloat *ex, + GLfloat *ey) +{ + int i; + for (i = 0; i < surf->n; i++) { + ex[i] = clip(surf->x[i], ctx->clip.x1, ctx->clip.x2); + ey[i] = clip(surf->y[i], ctx->clip.y1, ctx->clip.y2); + } + return surf->n; +} + +int +clip_transformed(struct clip_context *ctx, + struct polygon8 *surf, + GLfloat *ex, + GLfloat *ey) +{ + struct polygon8 polygon; + int i, n; + + polygon.n = clip_polygon_left(ctx, surf, polygon.x, polygon.y); + surf->n = clip_polygon_right(ctx, &polygon, surf->x, surf->y); + polygon.n = clip_polygon_top(ctx, surf, polygon.x, polygon.y); + surf->n = clip_polygon_bottom(ctx, &polygon, surf->x, surf->y); + + /* Get rid of duplicate vertices */ + ex[0] = surf->x[0]; + ey[0] = surf->y[0]; + n = 1; + for (i = 1; i < surf->n; i++) { + if (float_difference(ex[n - 1], surf->x[i]) == 0.0f && + float_difference(ey[n - 1], surf->y[i]) == 0.0f) + continue; + ex[n] = surf->x[i]; + ey[n] = surf->y[i]; + n++; + } + if (float_difference(ex[n - 1], surf->x[0]) == 0.0f && + float_difference(ey[n - 1], surf->y[0]) == 0.0f) + n--; + + return n; +} diff --git a/src/vertex-clipping.h b/src/vertex-clipping.h new file mode 100644 index 00000000..a16b1dfc --- /dev/null +++ b/src/vertex-clipping.h @@ -0,0 +1,65 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef _WESTON_VERTEX_CLIPPING_H +#define _WESTON_VERTEX_CLIPPING_H + +#include + +struct polygon8 { + GLfloat x[8]; + GLfloat y[8]; + int n; +}; + +struct clip_context { + struct { + GLfloat x; + GLfloat y; + } prev; + + struct { + GLfloat x1, y1; + GLfloat x2, y2; + } clip; + + struct { + GLfloat *x; + GLfloat *y; + } vertices; +}; + +GLfloat +float_difference(GLfloat a, GLfloat b); + +int +clip_simple(struct clip_context *ctx, + struct polygon8 *surf, + GLfloat *ex, + GLfloat *ey); + +int +clip_transformed(struct clip_context *ctx, + struct polygon8 *surf, + GLfloat *ex, + GLfloat *ey);\ + +#endif diff --git a/src/weston-egl-ext.h b/src/weston-egl-ext.h new file mode 100644 index 00000000..8ddf97f0 --- /dev/null +++ b/src/weston-egl-ext.h @@ -0,0 +1,80 @@ +/************************************************************************** + * + * Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas. + * 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, sub license, 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. + * + **************************************************************************/ + +/* Extensions used by Weston, copied from Mesa's eglmesaext.h, */ + +#ifndef WESTON_EGL_EXT_H +#define WESTON_EGL_EXT_H + +#ifndef EGL_WL_bind_wayland_display +#define EGL_WL_bind_wayland_display 1 + +#define EGL_WAYLAND_BUFFER_WL 0x31D5 /* eglCreateImageKHR target */ +#define EGL_WAYLAND_PLANE_WL 0x31D6 /* eglCreateImageKHR target */ + +#define EGL_TEXTURE_Y_U_V_WL 0x31D7 +#define EGL_TEXTURE_Y_UV_WL 0x31D8 +#define EGL_TEXTURE_Y_XUXV_WL 0x31D9 +#define EGL_TEXTURE_EXTERNAL_WL 0x31DA + +struct wl_display; +struct wl_resource; +#ifdef EGL_EGLEXT_PROTOTYPES +EGLAPI EGLBoolean EGLAPIENTRY eglBindWaylandDisplayWL(EGLDisplay dpy, struct wl_display *display); +EGLAPI EGLBoolean EGLAPIENTRY eglUnbindWaylandDisplayWL(EGLDisplay dpy, struct wl_display *display); +EGLAPI EGLBoolean EGLAPIENTRY eglQueryWaylandBufferWL(EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); +#endif +typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNBINDWAYLANDDISPLAYWL) (EGLDisplay dpy, struct wl_display *display); +typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWL) (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value); + +#endif + +#ifndef EGL_TEXTURE_EXTERNAL_WL +#define EGL_TEXTURE_EXTERNAL_WL 0x31DA +#endif + +#ifndef EGL_BUFFER_AGE_EXT +#define EGL_BUFFER_AGE_EXT 0x313D +#endif + +#ifndef EGL_WAYLAND_Y_INVERTED_WL +#define EGL_WAYLAND_Y_INVERTED_WL 0x31DB /* eglQueryWaylandBufferWL attribute */ +#endif + +/* Mesas gl2ext.h and probably Khronos upstream defined + * GL_EXT_unpack_subimage with non _EXT suffixed GL_UNPACK_* tokens. + * In case we're using that mess, manually define the _EXT versions + * of the tokens here.*/ +#if defined(GL_EXT_unpack_subimage) && !defined(GL_UNPACK_ROW_LENGTH_EXT) +#define GL_UNPACK_ROW_LENGTH_EXT 0x0CF2 +#define GL_UNPACK_SKIP_ROWS_EXT 0x0CF3 +#define GL_UNPACK_SKIP_PIXELS_EXT 0x0CF4 +#endif + + +#endif diff --git a/src/weston-launch.c b/src/weston-launch.c new file mode 100644 index 00000000..d8364c85 --- /dev/null +++ b/src/weston-launch.c @@ -0,0 +1,750 @@ +/* + * Copyright © 2012 Benjamin Franzke + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#ifdef HAVE_SYSTEMD_LOGIN +#include +#endif + +#include "weston-launch.h" + +#define DRM_MAJOR 226 + +#ifndef KDSKBMUTE +#define KDSKBMUTE 0x4B51 +#endif + +#ifndef EVIOCREVOKE +#define EVIOCREVOKE _IOW('E', 0x91, int) +#endif + +#define MAX_ARGV_SIZE 256 + +struct weston_launch { + struct pam_conv pc; + pam_handle_t *ph; + int tty; + int ttynr; + int sock[2]; + int drm_fd; + int last_input_fd; + int kb_mode; + struct passwd *pw; + + int signalfd; + + pid_t child; + int verbose; + char *new_user; +}; + +union cmsg_data { unsigned char b[4]; int fd; }; + +static gid_t * +read_groups(void) +{ + int n; + gid_t *groups; + + n = getgroups(0, NULL); + + if (n < 0) { + fprintf(stderr, "Unable to retrieve groups: %m\n"); + return NULL; + } + + groups = malloc(n * sizeof(gid_t)); + if (!groups) + return NULL; + + if (getgroups(n, groups) < 0) { + fprintf(stderr, "Unable to retrieve groups: %m\n"); + free(groups); + return NULL; + } + return groups; +} + +static int +weston_launch_allowed(struct weston_launch *wl) +{ + struct group *gr; + gid_t *groups; + int i; +#ifdef HAVE_SYSTEMD_LOGIN + char *session, *seat; + int err; +#endif + + if (getuid() == 0) + return 1; + + gr = getgrnam("weston-launch"); + if (gr) { + groups = read_groups(); + if (groups) { + for (i = 0; groups[i]; ++i) { + if (groups[i] == gr->gr_gid) { + free(groups); + return 1; + } + } + free(groups); + } + } + +#ifdef HAVE_SYSTEMD_LOGIN + err = sd_pid_get_session(getpid(), &session); + if (err == 0 && session) { + if (sd_session_is_active(session) && + sd_session_get_seat(session, &seat) == 0) { + free(seat); + free(session); + return 1; + } + free(session); + } +#endif + + return 0; +} + +static int +pam_conversation_fn(int msg_count, + const struct pam_message **messages, + struct pam_response **responses, + void *user_data) +{ + return PAM_SUCCESS; +} + +static int +setup_pam(struct weston_launch *wl) +{ + int err; + + wl->pc.conv = pam_conversation_fn; + wl->pc.appdata_ptr = wl; + + err = pam_start("login", wl->pw->pw_name, &wl->pc, &wl->ph); + if (err != PAM_SUCCESS) { + fprintf(stderr, "failed to start pam transaction: %d: %s\n", + err, pam_strerror(wl->ph, err)); + return -1; + } + + err = pam_set_item(wl->ph, PAM_TTY, ttyname(wl->tty)); + if (err != PAM_SUCCESS) { + fprintf(stderr, "failed to set PAM_TTY item: %d: %s\n", + err, pam_strerror(wl->ph, err)); + return -1; + } + + err = pam_open_session(wl->ph, 0); + if (err != PAM_SUCCESS) { + fprintf(stderr, "failed to open pam session: %d: %s\n", + err, pam_strerror(wl->ph, err)); + return -1; + } + + return 0; +} + +static int +setup_launcher_socket(struct weston_launch *wl) +{ + if (socketpair(AF_LOCAL, SOCK_SEQPACKET, 0, wl->sock) < 0) + error(1, errno, "socketpair failed"); + + if (fcntl(wl->sock[0], F_SETFD, FD_CLOEXEC) < 0) + error(1, errno, "fcntl failed"); + + return 0; +} + +static int +setup_signals(struct weston_launch *wl) +{ + int ret; + sigset_t mask; + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + sa.sa_handler = SIG_DFL; + sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; + ret = sigaction(SIGCHLD, &sa, NULL); + assert(ret == 0); + + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction(SIGHUP, &sa, NULL); + + ret = sigemptyset(&mask); + assert(ret == 0); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGUSR1); + sigaddset(&mask, SIGUSR2); + ret = sigprocmask(SIG_BLOCK, &mask, NULL); + assert(ret == 0); + + wl->signalfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (wl->signalfd < 0) + return -errno; + + return 0; +} + +static void +setenv_fd(const char *env, int fd) +{ + char buf[32]; + + snprintf(buf, sizeof buf, "%d", fd); + setenv(env, buf, 1); +} + +static int +send_reply(struct weston_launch *wl, int reply) +{ + int len; + + do { + len = send(wl->sock[0], &reply, sizeof reply, 0); + } while (len < 0 && errno == EINTR); + + return len; +} + +static int +handle_open(struct weston_launch *wl, struct msghdr *msg, ssize_t len) +{ + int fd = -1, ret = -1; + char control[CMSG_SPACE(sizeof(fd))]; + struct cmsghdr *cmsg; + struct stat s; + struct msghdr nmsg; + struct iovec iov; + struct weston_launcher_open *message; + union cmsg_data *data; + + message = msg->msg_iov->iov_base; + if ((size_t)len < sizeof(*message)) + goto err0; + + /* Ensure path is null-terminated */ + ((char *) message)[len-1] = '\0'; + + fd = open(message->path, message->flags); + if (fd < 0) { + fprintf(stderr, "Error opening device %s: %m\n", + message->path); + goto err0; + } + + if (fstat(fd, &s) < 0) { + close(fd); + fd = -1; + fprintf(stderr, "Failed to stat %s\n", message->path); + goto err0; + } + + if (major(s.st_rdev) != INPUT_MAJOR && + major(s.st_rdev) != DRM_MAJOR) { + close(fd); + fd = -1; + fprintf(stderr, "Device %s is not an input or drm device\n", + message->path); + goto err0; + } + +err0: + memset(&nmsg, 0, sizeof nmsg); + nmsg.msg_iov = &iov; + nmsg.msg_iovlen = 1; + if (fd != -1) { + nmsg.msg_control = control; + nmsg.msg_controllen = sizeof control; + cmsg = CMSG_FIRSTHDR(&nmsg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + data = (union cmsg_data *) CMSG_DATA(cmsg); + data->fd = fd; + nmsg.msg_controllen = cmsg->cmsg_len; + ret = 0; + } + iov.iov_base = &ret; + iov.iov_len = sizeof ret; + + if (wl->verbose) + fprintf(stderr, "weston-launch: opened %s: ret: %d, fd: %d\n", + message->path, ret, fd); + do { + len = sendmsg(wl->sock[0], &nmsg, 0); + } while (len < 0 && errno == EINTR); + + if (len < 0) + return -1; + + if (fd != -1 && major(s.st_rdev) == DRM_MAJOR) + wl->drm_fd = fd; + if (fd != -1 && major(s.st_rdev) == INPUT_MAJOR && + wl->last_input_fd < fd) + wl->last_input_fd = fd; + + return 0; +} + +static int +handle_socket_msg(struct weston_launch *wl) +{ + char control[CMSG_SPACE(sizeof(int))]; + char buf[BUFSIZ]; + struct msghdr msg; + struct iovec iov; + int ret = -1; + ssize_t len; + struct weston_launcher_message *message; + + memset(&msg, 0, sizeof(msg)); + iov.iov_base = buf; + iov.iov_len = sizeof buf; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof control; + + do { + len = recvmsg(wl->sock[0], &msg, 0); + } while (len < 0 && errno == EINTR); + + if (len < 1) + return -1; + + message = (void *) buf; + switch (message->opcode) { + case WESTON_LAUNCHER_OPEN: + ret = handle_open(wl, &msg, len); + break; + } + + return ret; +} + +static void +quit(struct weston_launch *wl, int status) +{ + struct vt_mode mode = { 0 }; + int err; + + close(wl->signalfd); + close(wl->sock[0]); + + if (wl->new_user) { + err = pam_close_session(wl->ph, 0); + if (err) + fprintf(stderr, "pam_close_session failed: %d: %s\n", + err, pam_strerror(wl->ph, err)); + pam_end(wl->ph, err); + } + + if (ioctl(wl->tty, KDSKBMUTE, 0) && + ioctl(wl->tty, KDSKBMODE, wl->kb_mode)) + fprintf(stderr, "failed to restore keyboard mode: %m\n"); + + if (ioctl(wl->tty, KDSETMODE, KD_TEXT)) + fprintf(stderr, "failed to set KD_TEXT mode on tty: %m\n"); + + /* We have to drop master before we switch the VT back in + * VT_AUTO, so we don't risk switching to a VT with another + * display server, that will then fail to set drm master. */ + drmDropMaster(wl->drm_fd); + + mode.mode = VT_AUTO; + if (ioctl(wl->tty, VT_SETMODE, &mode) < 0) + fprintf(stderr, "could not reset vt handling\n"); + + exit(status); +} + +static void +close_input_fds(struct weston_launch *wl) +{ + struct stat s; + int fd; + + for (fd = 3; fd <= wl->last_input_fd; fd++) { + if (fstat(fd, &s) == 0 && major(s.st_rdev) == INPUT_MAJOR) { + /* EVIOCREVOKE may fail if the kernel doesn't + * support it, but all we can do is ignore it. */ + ioctl(fd, EVIOCREVOKE, 0); + close(fd); + } + } +} + +static int +handle_signal(struct weston_launch *wl) +{ + struct signalfd_siginfo sig; + int pid, status, ret; + + if (read(wl->signalfd, &sig, sizeof sig) != sizeof sig) { + error(0, errno, "reading signalfd failed"); + return -1; + } + + switch (sig.ssi_signo) { + case SIGCHLD: + pid = waitpid(-1, &status, 0); + if (pid == wl->child) { + wl->child = 0; + if (WIFEXITED(status)) + ret = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + /* + * If weston dies because of signal N, we + * return 10+N. This is distinct from + * weston-launch dying because of a signal + * (128+N). + */ + ret = 10 + WTERMSIG(status); + else + ret = 0; + quit(wl, ret); + } + break; + case SIGTERM: + case SIGINT: + if (wl->child) + kill(wl->child, sig.ssi_signo); + break; + case SIGUSR1: + send_reply(wl, WESTON_LAUNCHER_DEACTIVATE); + close_input_fds(wl); + drmDropMaster(wl->drm_fd); + ioctl(wl->tty, VT_RELDISP, 1); + break; + case SIGUSR2: + ioctl(wl->tty, VT_RELDISP, VT_ACKACQ); + drmSetMaster(wl->drm_fd); + send_reply(wl, WESTON_LAUNCHER_ACTIVATE); + break; + default: + return -1; + } + + return 0; +} + +static int +setup_tty(struct weston_launch *wl, const char *tty) +{ + struct stat buf; + struct vt_mode mode = { 0 }; + char *t; + + if (!wl->new_user) { + wl->tty = STDIN_FILENO; + } else if (tty) { + t = ttyname(STDIN_FILENO); + if (t && strcmp(t, tty) == 0) + wl->tty = STDIN_FILENO; + else + wl->tty = open(tty, O_RDWR | O_NOCTTY); + } else { + int tty0 = open("/dev/tty0", O_WRONLY | O_CLOEXEC); + char filename[16]; + + if (tty0 < 0) + error(1, errno, "could not open tty0"); + + if (ioctl(tty0, VT_OPENQRY, &wl->ttynr) < 0 || wl->ttynr == -1) + error(1, errno, "failed to find non-opened console"); + + snprintf(filename, sizeof filename, "/dev/tty%d", wl->ttynr); + wl->tty = open(filename, O_RDWR | O_NOCTTY); + close(tty0); + } + + if (wl->tty < 0) + error(1, errno, "failed to open tty"); + + if (fstat(wl->tty, &buf) == -1 || + major(buf.st_rdev) != TTY_MAJOR || minor(buf.st_rdev) == 0) + error(1, 0, "weston-launch must be run from a virtual terminal"); + + if (tty) { + if (fstat(wl->tty, &buf) < 0) + error(1, errno, "stat %s failed", tty); + + if (major(buf.st_rdev) != TTY_MAJOR) + error(1, 0, "invalid tty device: %s", tty); + + wl->ttynr = minor(buf.st_rdev); + } + + if (ioctl(wl->tty, KDGKBMODE, &wl->kb_mode)) + error(1, errno, "failed to get current keyboard mode: %m\n"); + + if (ioctl(wl->tty, KDSKBMUTE, 1) && + ioctl(wl->tty, KDSKBMODE, K_OFF)) + error(1, errno, "failed to set K_OFF keyboard mode: %m\n"); + + if (ioctl(wl->tty, KDSETMODE, KD_GRAPHICS)) + error(1, errno, "failed to set KD_GRAPHICS mode on tty: %m\n"); + + mode.mode = VT_PROCESS; + mode.relsig = SIGUSR1; + mode.acqsig = SIGUSR2; + if (ioctl(wl->tty, VT_SETMODE, &mode) < 0) + error(1, errno, "failed to take control of vt handling\n"); + + return 0; +} + +static void +setup_session(struct weston_launch *wl) +{ + char **env; + char *term; + int i; + + if (wl->tty != STDIN_FILENO) { + if (setsid() < 0) + error(1, errno, "setsid failed"); + if (ioctl(wl->tty, TIOCSCTTY, 0) < 0) + error(1, errno, "TIOCSCTTY failed - tty is in use"); + } + + term = getenv("TERM"); + clearenv(); + if (term) + setenv("TERM", term, 1); + setenv("USER", wl->pw->pw_name, 1); + setenv("LOGNAME", wl->pw->pw_name, 1); + setenv("HOME", wl->pw->pw_dir, 1); + setenv("SHELL", wl->pw->pw_shell, 1); + + env = pam_getenvlist(wl->ph); + if (env) { + for (i = 0; env[i]; ++i) { + if (putenv(env[i]) < 0) + error(0, 0, "putenv %s failed", env[i]); + } + free(env); + } +} + +static void +drop_privileges(struct weston_launch *wl) +{ + if (setgid(wl->pw->pw_gid) < 0 || +#ifdef HAVE_INITGROUPS + initgroups(wl->pw->pw_name, wl->pw->pw_gid) < 0 || +#endif + setuid(wl->pw->pw_uid) < 0) + error(1, errno, "dropping privileges failed"); +} + +static void +launch_compositor(struct weston_launch *wl, int argc, char *argv[]) +{ + char *child_argv[MAX_ARGV_SIZE]; + sigset_t mask; + int i; + + if (wl->verbose) + printf("weston-launch: spawned weston with pid: %d\n", getpid()); + if (wl->new_user) + setup_session(wl); + + if (geteuid() == 0) + drop_privileges(wl); + + setenv_fd("WESTON_TTY_FD", wl->tty); + setenv_fd("WESTON_LAUNCHER_SOCK", wl->sock[1]); + + unsetenv("DISPLAY"); + + /* Do not give our signal mask to the new process. */ + sigemptyset(&mask); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGINT); + sigprocmask(SIG_UNBLOCK, &mask, NULL); + + child_argv[0] = "/bin/sh"; + child_argv[1] = "-l"; + child_argv[2] = "-c"; + child_argv[3] = BINDIR "/weston \"$@\""; + child_argv[4] = "weston"; + for (i = 0; i < argc; ++i) + child_argv[5 + i] = argv[i]; + child_argv[5 + i] = NULL; + + execv(child_argv[0], child_argv); + error(1, errno, "exec failed"); +} + +static void +help(const char *name) +{ + fprintf(stderr, "Usage: %s [args...] [-- [weston args..]]\n", name); + fprintf(stderr, " -u, --user Start session as specified username\n"); + fprintf(stderr, " -t, --tty Start session on alternative tty\n"); + fprintf(stderr, " -v, --verbose Be verbose\n"); + fprintf(stderr, " -h, --help Display this help message\n"); +} + +int +main(int argc, char *argv[]) +{ + struct weston_launch wl; + int i, c; + char *tty = NULL; + struct option opts[] = { + { "user", required_argument, NULL, 'u' }, + { "tty", required_argument, NULL, 't' }, + { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { 0, 0, NULL, 0 } + }; + + memset(&wl, 0, sizeof wl); + + while ((c = getopt_long(argc, argv, "u:t::vh", opts, &i)) != -1) { + switch (c) { + case 'u': + wl.new_user = optarg; + if (getuid() != 0) + error(1, 0, "Permission denied. -u allowed for root only"); + break; + case 't': + tty = optarg; + break; + case 'v': + wl.verbose = 1; + break; + case 'h': + help("weston-launch"); + exit(EXIT_FAILURE); + } + } + + if ((argc - optind) > (MAX_ARGV_SIZE - 6)) + error(1, E2BIG, "Too many arguments to pass to weston"); + + if (wl.new_user) + wl.pw = getpwnam(wl.new_user); + else + wl.pw = getpwuid(getuid()); + if (wl.pw == NULL) + error(1, errno, "failed to get username"); + + if (!weston_launch_allowed(&wl)) + error(1, 0, "Permission denied. You should either:\n" +#ifdef HAVE_SYSTEMD_LOGIN + " - run from an active and local (systemd) session.\n" +#else + " - enable systemd session support for weston-launch.\n" +#endif + " - or add yourself to the 'weston-launch' group."); + + if (setup_tty(&wl, tty) < 0) + exit(EXIT_FAILURE); + + if (wl.new_user && setup_pam(&wl) < 0) + exit(EXIT_FAILURE); + + if (setup_launcher_socket(&wl) < 0) + exit(EXIT_FAILURE); + + if (setup_signals(&wl) < 0) + exit(EXIT_FAILURE); + + wl.child = fork(); + if (wl.child == -1) { + error(1, errno, "fork failed"); + exit(EXIT_FAILURE); + } + + if (wl.child == 0) + launch_compositor(&wl, argc - optind, argv + optind); + + close(wl.sock[1]); + if (wl.tty != STDIN_FILENO) + close(wl.tty); + + while (1) { + struct pollfd fds[2]; + int n; + + fds[0].fd = wl.sock[0]; + fds[0].events = POLLIN; + fds[1].fd = wl.signalfd; + fds[1].events = POLLIN; + + n = poll(fds, 2, -1); + if (n < 0) + error(0, errno, "poll failed"); + if (fds[0].revents & POLLIN) + handle_socket_msg(&wl); + if (fds[1].revents) + handle_signal(&wl); + } + + return 0; +} diff --git a/src/weston-launch.h b/src/weston-launch.h new file mode 100644 index 00000000..e20c4c71 --- /dev/null +++ b/src/weston-launch.h @@ -0,0 +1,46 @@ +/* + * Copyright © 2012 Benjamin Franzke + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _WESTON_LAUNCH_H_ +#define _WESTON_LAUNCH_H_ + +enum weston_launcher_opcode { + WESTON_LAUNCHER_OPEN, +}; + +enum weston_launcher_event { + WESTON_LAUNCHER_SUCCESS, + WESTON_LAUNCHER_ACTIVATE, + WESTON_LAUNCHER_DEACTIVATE +}; + +struct weston_launcher_message { + int opcode; +}; + +struct weston_launcher_open { + struct weston_launcher_message header; + int flags; + char path[0]; +}; + +#endif diff --git a/src/weston.pc.in b/src/weston.pc.in new file mode 100644 index 00000000..828cb1f0 --- /dev/null +++ b/src/weston.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +libexecdir=@libexecdir@ +pkglibexecdir=${libexecdir}/@PACKAGE@ + +Name: Weston Plugin API +Description: Header files for Weston plugin development +Version: @WESTON_VERSION@ +Cflags: -I${includedir} diff --git a/src/xwayland/Makefile.am b/src/xwayland/Makefile.am new file mode 100644 index 00000000..cab59c70 --- /dev/null +++ b/src/xwayland/Makefile.am @@ -0,0 +1,40 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/shared \ + -I$(top_builddir)/src \ + -DDATADIR='"$(datadir)"' \ + -DMODULEDIR='"$(moduledir)"' \ + -DLIBEXECDIR='"$(libexecdir)"' \ + -DXSERVER_PATH='"@XSERVER_PATH@"' + +moduledir = @libdir@/weston +module_LTLIBRARIES = xwayland.la + +xwayland = xwayland.la +xwayland_la_LDFLAGS = -module -avoid-version +xwayland_la_LIBADD = \ + $(XWAYLAND_LIBS) \ + $(top_builddir)/shared/libshared-cairo.la +xwayland_la_CFLAGS = \ + $(GCC_CFLAGS) \ + $(COMPOSITOR_CFLAGS) \ + $(PIXMAN_CFLAGS) \ + $(CAIRO_CFLAGS) +xwayland_la_SOURCES = \ + xwayland.h \ + window-manager.c \ + selection.c \ + dnd.c \ + launcher.c \ + xserver-protocol.c \ + xserver-server-protocol.h \ + hash.c \ + hash.h + +BUILT_SOURCES = \ + xserver-protocol.c \ + xserver-server-protocol.h + +CLEANFILES = $(BUILT_SOURCES) + +wayland_protocoldir = $(top_srcdir)/protocol +include $(top_srcdir)/wayland-scanner.mk diff --git a/src/xwayland/dnd.c b/src/xwayland/dnd.c new file mode 100644 index 00000000..b146e123 --- /dev/null +++ b/src/xwayland/dnd.c @@ -0,0 +1,274 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xwayland.h" + +#include "../../shared/cairo-util.h" +#include "../compositor.h" +#include "xserver-server-protocol.h" +#include "hash.h" + +static void +weston_dnd_start(struct weston_wm *wm, xcb_window_t owner) +{ + uint32_t values[1], version = 4; + + wm->dnd_window = xcb_generate_id(wm->conn); + values[0] = + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_PROPERTY_CHANGE; + + xcb_create_window(wm->conn, + XCB_COPY_FROM_PARENT, + wm->dnd_window, + wm->screen->root, + 0, 0, + 8192, 8192, + 0, + XCB_WINDOW_CLASS_INPUT_ONLY, + wm->screen->root_visual, + XCB_CW_EVENT_MASK, values); + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + wm->dnd_window, + wm->atom.xdnd_aware, + XCB_ATOM_ATOM, + 32, /* format */ + 1, &version); + + xcb_map_window(wm->conn, wm->dnd_window); + wm->dnd_owner = owner; +} + +static void +weston_dnd_stop(struct weston_wm *wm) +{ + xcb_destroy_window(wm->conn, wm->dnd_window); + wm->dnd_window = XCB_WINDOW_NONE; +} + +struct dnd_data_source { + struct weston_data_source base; + struct weston_wm *wm; + int version; + uint32_t window; +}; + +static void +data_source_accept(struct weston_data_source *base, + uint32_t time, const char *mime_type) +{ + struct dnd_data_source *source = (struct dnd_data_source *) base; + xcb_client_message_event_t client_message; + struct weston_wm *wm = source->wm; + + weston_log("got accept, mime-type %s\n", mime_type); + + /* FIXME: If we rewrote UTF8_STRING to + * text/plain;charset=utf-8 and the source doesn't support the + * mime-type, we'll have to rewrite the mime-type back to + * UTF8_STRING here. */ + + client_message.response_type = XCB_CLIENT_MESSAGE; + client_message.format = 32; + client_message.window = wm->dnd_window; + client_message.type = wm->atom.xdnd_status; + client_message.data.data32[0] = wm->dnd_window; + client_message.data.data32[1] = 2; + if (mime_type) + client_message.data.data32[1] |= 1; + client_message.data.data32[2] = 0; + client_message.data.data32[3] = 0; + client_message.data.data32[4] = wm->atom.xdnd_action_copy; + + xcb_send_event(wm->conn, 0, wm->dnd_owner, + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + (char *) &client_message); +} + +static void +data_source_send(struct weston_data_source *base, + const char *mime_type, int32_t fd) +{ + struct dnd_data_source *source = (struct dnd_data_source *) base; + struct weston_wm *wm = source->wm; + + weston_log("got send, %s\n", mime_type); + + /* Get data for the utf8_string target */ + xcb_convert_selection(wm->conn, + wm->selection_window, + wm->atom.xdnd_selection, + wm->atom.utf8_string, + wm->atom.wl_selection, + XCB_TIME_CURRENT_TIME); + + xcb_flush(wm->conn); + + fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK); + wm->data_source_fd = fd; +} + +static void +data_source_cancel(struct weston_data_source *source) +{ + weston_log("got cancel\n"); +} + +static void +handle_enter(struct weston_wm *wm, xcb_client_message_event_t *client_message) +{ + struct dnd_data_source *source; + struct weston_seat *seat = weston_wm_pick_seat(wm); + char **p; + const char *name; + uint32_t *types; + int i, length, has_text; + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *reply; + + source = malloc(sizeof *source); + if (source == NULL) + return; + + wl_signal_init(&source->base.destroy_signal); + source->base.accept = data_source_accept; + source->base.send = data_source_send; + source->base.cancel = data_source_cancel; + source->wm = wm; + source->window = client_message->data.data32[0]; + source->version = client_message->data.data32[1] >> 24; + + if (client_message->data.data32[1] & 1) { + cookie = xcb_get_property(wm->conn, + 0, /* delete */ + source->window, + wm->atom.xdnd_type_list, + XCB_ATOM_ANY, 0, 2048); + reply = xcb_get_property_reply(wm->conn, cookie, NULL); + types = xcb_get_property_value(reply); + length = reply->value_len; + } else { + reply = NULL; + types = &client_message->data.data32[2]; + length = 3; + } + + wl_array_init(&source->base.mime_types); + has_text = 0; + for (i = 0; i < length; i++) { + if (types[i] == XCB_ATOM_NONE) + continue; + + name = get_atom_name(wm->conn, types[i]); + if (types[i] == wm->atom.utf8_string || + types[i] == wm->atom.text_plain_utf8 || + types[i] == wm->atom.text_plain) { + if (has_text) + continue; + + has_text = 1; + p = wl_array_add(&source->base.mime_types, sizeof *p); + if (p) + *p = strdup("text/plain;charset=utf-8"); + } else if (strchr(name, '/')) { + p = wl_array_add(&source->base.mime_types, sizeof *p); + if (p) + *p = strdup(name); + } + } + + free(reply); + weston_seat_start_drag(seat, &source->base, NULL, NULL); +} + +int +weston_wm_handle_dnd_event(struct weston_wm *wm, + xcb_generic_event_t *event) +{ + xcb_xfixes_selection_notify_event_t *xfixes_selection_notify = + (xcb_xfixes_selection_notify_event_t *) event; + xcb_client_message_event_t *client_message = + (xcb_client_message_event_t *) event; + + switch (event->response_type - wm->xfixes->first_event) { + case XCB_XFIXES_SELECTION_NOTIFY: + if (xfixes_selection_notify->selection != wm->atom.xdnd_selection) + return 0; + + weston_log("XdndSelection owner: %d!\n", + xfixes_selection_notify->owner); + + if (xfixes_selection_notify->owner != XCB_WINDOW_NONE) + weston_dnd_start(wm, xfixes_selection_notify->owner); + else + weston_dnd_stop(wm); + + return 1; + } + + switch (EVENT_TYPE(event)) { + case XCB_CLIENT_MESSAGE: + if (client_message->type == wm->atom.xdnd_enter) { + handle_enter(wm, client_message); + return 1; + } else if (client_message->type == wm->atom.xdnd_leave) { + weston_log("got leave!\n"); + return 1; + } else if (client_message->type == wm->atom.xdnd_drop) { + weston_log("got drop!\n"); + return 1; + } else if (client_message->type == wm->atom.xdnd_drop) { + weston_log("got enter!\n"); + return 1; + } + return 0; + } + + return 0; +} + +void +weston_wm_dnd_init(struct weston_wm *wm) +{ + uint32_t mask; + + mask = + XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE; + xcb_xfixes_select_selection_input(wm->conn, wm->selection_window, + wm->atom.xdnd_selection, mask); +} diff --git a/src/xwayland/hash.c b/src/xwayland/hash.c new file mode 100644 index 00000000..54f3de93 --- /dev/null +++ b/src/xwayland/hash.c @@ -0,0 +1,309 @@ +/* + * Copyright © 2009 Intel Corporation + * Copyright © 1988-2004 Keith Packard and Bart Massey. + * + * 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. + * + * Except as contained in this notice, the names of the authors + * or their institutions shall not be used in advertising or + * otherwise to promote the sale, use or other dealings in this + * Software without prior written authorization from the + * authors. + * + * Authors: + * Eric Anholt + * Keith Packard + */ + +#include + +#include +#include + +#include "hash.h" + +struct hash_entry { + uint32_t hash; + void *data; +}; + +struct hash_table { + struct hash_entry *table; + uint32_t size; + uint32_t rehash; + uint32_t max_entries; + uint32_t size_index; + uint32_t entries; + uint32_t deleted_entries; +}; + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) + +/* + * From Knuth -- a good choice for hash/rehash values is p, p-2 where + * p and p-2 are both prime. These tables are sized to have an extra 10% + * free to avoid exponential performance degradation as the hash table fills + */ + +static const uint32_t deleted_data; + +static const struct { + uint32_t max_entries, size, rehash; +} hash_sizes[] = { + { 2, 5, 3 }, + { 4, 7, 5 }, + { 8, 13, 11 }, + { 16, 19, 17 }, + { 32, 43, 41 }, + { 64, 73, 71 }, + { 128, 151, 149 }, + { 256, 283, 281 }, + { 512, 571, 569 }, + { 1024, 1153, 1151 }, + { 2048, 2269, 2267 }, + { 4096, 4519, 4517 }, + { 8192, 9013, 9011 }, + { 16384, 18043, 18041 }, + { 32768, 36109, 36107 }, + { 65536, 72091, 72089 }, + { 131072, 144409, 144407 }, + { 262144, 288361, 288359 }, + { 524288, 576883, 576881 }, + { 1048576, 1153459, 1153457 }, + { 2097152, 2307163, 2307161 }, + { 4194304, 4613893, 4613891 }, + { 8388608, 9227641, 9227639 }, + { 16777216, 18455029, 18455027 }, + { 33554432, 36911011, 36911009 }, + { 67108864, 73819861, 73819859 }, + { 134217728, 147639589, 147639587 }, + { 268435456, 295279081, 295279079 }, + { 536870912, 590559793, 590559791 }, + { 1073741824, 1181116273, 1181116271}, + { 2147483648ul, 2362232233ul, 2362232231ul} +}; + +static int +entry_is_free(struct hash_entry *entry) +{ + return entry->data == NULL; +} + +static int +entry_is_deleted(struct hash_entry *entry) +{ + return entry->data == &deleted_data; +} + +static int +entry_is_present(struct hash_entry *entry) +{ + return entry->data != NULL && entry->data != &deleted_data; +} + +struct hash_table * +hash_table_create(void) +{ + struct hash_table *ht; + + ht = malloc(sizeof(*ht)); + if (ht == NULL) + return NULL; + + ht->size_index = 0; + ht->size = hash_sizes[ht->size_index].size; + ht->rehash = hash_sizes[ht->size_index].rehash; + ht->max_entries = hash_sizes[ht->size_index].max_entries; + ht->table = calloc(ht->size, sizeof(*ht->table)); + ht->entries = 0; + ht->deleted_entries = 0; + + if (ht->table == NULL) { + free(ht); + return NULL; + } + + return ht; +} + +/** + * Frees the given hash table. + */ +void +hash_table_destroy(struct hash_table *ht) +{ + if (!ht) + return; + + free(ht->table); + free(ht); +} + +/** + * Finds a hash table entry with the given key and hash of that key. + * + * Returns NULL if no entry is found. Note that the data pointer may be + * modified by the user. + */ +static void * +hash_table_search(struct hash_table *ht, uint32_t hash) +{ + uint32_t hash_address; + + hash_address = hash % ht->size; + do { + uint32_t double_hash; + + struct hash_entry *entry = ht->table + hash_address; + + if (entry_is_free(entry)) { + return NULL; + } else if (entry_is_present(entry) && entry->hash == hash) { + return entry; + } + + double_hash = 1 + hash % ht->rehash; + + hash_address = (hash_address + double_hash) % ht->size; + } while (hash_address != hash % ht->size); + + return NULL; +} + +void +hash_table_for_each(struct hash_table *ht, + hash_table_iterator_func_t func, void *data) +{ + struct hash_entry *entry; + uint32_t i; + + for (i = 0; i < ht->size; i++) { + entry = ht->table + i; + if (entry_is_present(entry)) + func(entry->data, data); + } +} + +void * +hash_table_lookup(struct hash_table *ht, uint32_t hash) +{ + struct hash_entry *entry; + + entry = hash_table_search(ht, hash); + if (entry != NULL) + return entry->data; + + return NULL; +} + +static void +hash_table_rehash(struct hash_table *ht, unsigned int new_size_index) +{ + struct hash_table old_ht; + struct hash_entry *table, *entry; + + if (new_size_index >= ARRAY_SIZE(hash_sizes)) + return; + + table = calloc(hash_sizes[new_size_index].size, sizeof(*ht->table)); + if (table == NULL) + return; + + old_ht = *ht; + + ht->table = table; + ht->size_index = new_size_index; + ht->size = hash_sizes[ht->size_index].size; + ht->rehash = hash_sizes[ht->size_index].rehash; + ht->max_entries = hash_sizes[ht->size_index].max_entries; + ht->entries = 0; + ht->deleted_entries = 0; + + for (entry = old_ht.table; + entry != old_ht.table + old_ht.size; + entry++) { + if (entry_is_present(entry)) { + hash_table_insert(ht, entry->hash, entry->data); + } + } + + free(old_ht.table); +} + +/** + * Inserts the data with the given hash into the table. + * + * Note that insertion may rearrange the table on a resize or rehash, + * so previously found hash_entries are no longer valid after this function. + */ +int +hash_table_insert(struct hash_table *ht, uint32_t hash, void *data) +{ + uint32_t hash_address; + + if (ht->entries >= ht->max_entries) { + hash_table_rehash(ht, ht->size_index + 1); + } else if (ht->deleted_entries + ht->entries >= ht->max_entries) { + hash_table_rehash(ht, ht->size_index); + } + + hash_address = hash % ht->size; + do { + struct hash_entry *entry = ht->table + hash_address; + uint32_t double_hash; + + if (!entry_is_present(entry)) { + if (entry_is_deleted(entry)) + ht->deleted_entries--; + entry->hash = hash; + entry->data = data; + ht->entries++; + return 0; + } + + double_hash = 1 + hash % ht->rehash; + + hash_address = (hash_address + double_hash) % ht->size; + } while (hash_address != hash % ht->size); + + /* We could hit here if a required resize failed. An unchecked-malloc + * application could ignore this result. + */ + return -1; +} + +/** + * This function deletes the given hash table entry. + * + * Note that deletion doesn't otherwise modify the table, so an iteration over + * the table deleting entries is safe. + */ +void +hash_table_remove(struct hash_table *ht, uint32_t hash) +{ + struct hash_entry *entry; + + entry = hash_table_search(ht, hash); + if (entry != NULL) { + entry->data = (void *) &deleted_data; + ht->entries--; + ht->deleted_entries++; + } +} diff --git a/src/xwayland/hash.h b/src/xwayland/hash.h new file mode 100644 index 00000000..6e1674e1 --- /dev/null +++ b/src/xwayland/hash.h @@ -0,0 +1,49 @@ +/* + * Copyright © 2009 Intel Corporation + * Copyright © 1988-2004 Keith Packard and Bart Massey. + * + * 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. + * + * Except as contained in this notice, the names of the authors + * or their institutions shall not be used in advertising or + * otherwise to promote the sale, use or other dealings in this + * Software without prior written authorization from the + * authors. + * + * Authors: + * Eric Anholt + * Keith Packard + */ + +#ifndef HASH_H +#define HASH_H + +struct hash_table; +struct hash_table *hash_table_create(void); +typedef void (*hash_table_iterator_func_t)(void *element, void *data); + +void hash_table_destroy(struct hash_table *ht); +void *hash_table_lookup(struct hash_table *ht, uint32_t hash); +int hash_table_insert(struct hash_table *ht, uint32_t hash, void *data); +void hash_table_remove(struct hash_table *ht, uint32_t hash); +void hash_table_for_each(struct hash_table *ht, + hash_table_iterator_func_t func, void *data); + +#endif diff --git a/src/xwayland/launcher.c b/src/xwayland/launcher.c new file mode 100644 index 00000000..8d8e060a --- /dev/null +++ b/src/xwayland/launcher.c @@ -0,0 +1,389 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xwayland.h" +#include "xserver-server-protocol.h" + + +static int +weston_xserver_handle_event(int listen_fd, uint32_t mask, void *data) +{ + struct weston_xserver *wxs = data; + char display[8], s[8]; + int sv[2], client_fd; + char *xserver = NULL; + struct weston_config_section *section; + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { + weston_log("socketpair failed\n"); + return 1; + } + + wxs->process.pid = fork(); + switch (wxs->process.pid) { + case 0: + /* SOCK_CLOEXEC closes both ends, so we need to unset + * the flag on the client fd. */ + client_fd = dup(sv[1]); + if (client_fd < 0) + return 1; + + snprintf(s, sizeof s, "%d", client_fd); + setenv("WAYLAND_SOCKET", s, 1); + + snprintf(display, sizeof display, ":%d", wxs->display); + + section = weston_config_get_section(wxs->compositor->config, "xwayland", NULL, NULL); + weston_config_section_get_string(section, "path", &xserver, XSERVER_PATH); + + if (execl(xserver, + xserver, + display, + "-wayland", + "-rootless", + "-retro", + "-nolisten", "all", + "-terminate", + NULL) < 0) + weston_log("exec failed: %m\n"); + free(xserver); + _exit(EXIT_FAILURE); + + default: + weston_log("forked X server, pid %d\n", wxs->process.pid); + + close(sv[1]); + wxs->client = wl_client_create(wxs->wl_display, sv[0]); + + weston_watch_process(&wxs->process); + + wl_event_source_remove(wxs->abstract_source); + wl_event_source_remove(wxs->unix_source); + break; + + case -1: + weston_log( "failed to fork\n"); + break; + } + + return 1; +} + +static void +weston_xserver_shutdown(struct weston_xserver *wxs) +{ + char path[256]; + + snprintf(path, sizeof path, "/tmp/.X%d-lock", wxs->display); + unlink(path); + snprintf(path, sizeof path, "/tmp/.X11-unix/X%d", wxs->display); + unlink(path); + if (wxs->process.pid == 0) { + wl_event_source_remove(wxs->abstract_source); + wl_event_source_remove(wxs->unix_source); + } + close(wxs->abstract_fd); + close(wxs->unix_fd); + if (wxs->wm) + weston_wm_destroy(wxs->wm); + wxs->loop = NULL; +} + +static void +weston_xserver_cleanup(struct weston_process *process, int status) +{ + struct weston_xserver *wxs = + container_of(process, struct weston_xserver, process); + + wxs->process.pid = 0; + wxs->client = NULL; + wxs->resource = NULL; + + wxs->abstract_source = + wl_event_loop_add_fd(wxs->loop, wxs->abstract_fd, + WL_EVENT_READABLE, + weston_xserver_handle_event, wxs); + + wxs->unix_source = + wl_event_loop_add_fd(wxs->loop, wxs->unix_fd, + WL_EVENT_READABLE, + weston_xserver_handle_event, wxs); + + if (wxs->wm) { + weston_log("xserver exited, code %d\n", status); + weston_wm_destroy(wxs->wm); + wxs->wm = NULL; + } else { + /* If the X server crashes before it binds to the + * xserver interface, shut down and don't try + * again. */ + weston_log("xserver crashing too fast: %d\n", status); + weston_xserver_shutdown(wxs); + } +} + +static void +bind_xserver(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct weston_xserver *wxs = data; + + /* If it's a different client than the xserver we launched, + * don't start the wm. */ + if (client != wxs->client) + return; + + wxs->resource = + wl_resource_create(client, &xserver_interface, + 1, id); + wl_resource_set_implementation(wxs->resource, &xserver_implementation, + wxs, NULL); + + wxs->wm = weston_wm_create(wxs); + if (wxs->wm == NULL) { + weston_log("failed to create wm\n"); + } + + xserver_send_listen_socket(wxs->resource, wxs->abstract_fd); + xserver_send_listen_socket(wxs->resource, wxs->unix_fd); +} + +static int +bind_to_abstract_socket(int display) +{ + struct sockaddr_un addr; + socklen_t size, name_size; + int fd; + + fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return -1; + + addr.sun_family = AF_LOCAL; + name_size = snprintf(addr.sun_path, sizeof addr.sun_path, + "%c/tmp/.X11-unix/X%d", 0, display); + size = offsetof(struct sockaddr_un, sun_path) + name_size; + if (bind(fd, (struct sockaddr *) &addr, size) < 0) { + weston_log("failed to bind to @%s: %s\n", + addr.sun_path + 1, strerror(errno)); + close(fd); + return -1; + } + + if (listen(fd, 1) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static int +bind_to_unix_socket(int display) +{ + struct sockaddr_un addr; + socklen_t size, name_size; + int fd; + + fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return -1; + + addr.sun_family = AF_LOCAL; + name_size = snprintf(addr.sun_path, sizeof addr.sun_path, + "/tmp/.X11-unix/X%d", display) + 1; + size = offsetof(struct sockaddr_un, sun_path) + name_size; + unlink(addr.sun_path); + if (bind(fd, (struct sockaddr *) &addr, size) < 0) { + weston_log("failed to bind to %s (%s)\n", + addr.sun_path, strerror(errno)); + close(fd); + return -1; + } + + if (listen(fd, 1) < 0) { + unlink(addr.sun_path); + close(fd); + return -1; + } + + return fd; +} + +static int +create_lockfile(int display, char *lockfile, size_t lsize) +{ + char pid[16], *end; + int fd, size; + pid_t other; + + snprintf(lockfile, lsize, "/tmp/.X%d-lock", display); + fd = open(lockfile, O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0444); + if (fd < 0 && errno == EEXIST) { + fd = open(lockfile, O_CLOEXEC, O_RDONLY); + if (fd < 0 || read(fd, pid, 11) != 11) { + weston_log("can't read lock file %s: %s\n", + lockfile, strerror(errno)); + if (fd >= 0) + close (fd); + + errno = EEXIST; + return -1; + } + + other = strtol(pid, &end, 0); + if (end != pid + 10) { + weston_log("can't parse lock file %s\n", + lockfile); + close(fd); + errno = EEXIST; + return -1; + } + + if (kill(other, 0) < 0 && errno == ESRCH) { + /* stale lock file; unlink and try again */ + weston_log("unlinking stale lock file %s\n", lockfile); + close(fd); + if (unlink(lockfile)) + /* If we fail to unlink, return EEXIST + so we try the next display number.*/ + errno = EEXIST; + else + errno = EAGAIN; + return -1; + } + + close(fd); + errno = EEXIST; + return -1; + } else if (fd < 0) { + weston_log("failed to create lock file %s: %s\n", + lockfile, strerror(errno)); + return -1; + } + + /* Subtle detail: we use the pid of the wayland + * compositor, not the xserver in the lock file. */ + size = snprintf(pid, sizeof pid, "%10d\n", getpid()); + if (write(fd, pid, size) != size) { + unlink(lockfile); + close(fd); + return -1; + } + + close(fd); + + return 0; +} + +static void +weston_xserver_destroy(struct wl_listener *l, void *data) +{ + struct weston_xserver *wxs = + container_of(l, struct weston_xserver, destroy_listener); + + if (!wxs) + return; + + if (wxs->loop) + weston_xserver_shutdown(wxs); + + free(wxs); +} + +WL_EXPORT int +module_init(struct weston_compositor *compositor, + int *argc, char *argv[]) + +{ + struct wl_display *display = compositor->wl_display; + struct weston_xserver *wxs; + char lockfile[256], display_name[8]; + + wxs = zalloc(sizeof *wxs); + wxs->process.cleanup = weston_xserver_cleanup; + wxs->wl_display = display; + wxs->compositor = compositor; + + wxs->display = 0; + + retry: + if (create_lockfile(wxs->display, lockfile, sizeof lockfile) < 0) { + if (errno == EAGAIN) { + goto retry; + } else if (errno == EEXIST) { + wxs->display++; + goto retry; + } else { + free(wxs); + return -1; + } + } + + wxs->abstract_fd = bind_to_abstract_socket(wxs->display); + if (wxs->abstract_fd < 0 && errno == EADDRINUSE) { + wxs->display++; + unlink(lockfile); + goto retry; + } + + wxs->unix_fd = bind_to_unix_socket(wxs->display); + if (wxs->unix_fd < 0) { + unlink(lockfile); + close(wxs->abstract_fd); + free(wxs); + return -1; + } + + snprintf(display_name, sizeof display_name, ":%d", wxs->display); + weston_log("xserver listening on display %s\n", display_name); + setenv("DISPLAY", display_name, 1); + + wxs->loop = wl_display_get_event_loop(display); + wxs->abstract_source = + wl_event_loop_add_fd(wxs->loop, wxs->abstract_fd, + WL_EVENT_READABLE, + weston_xserver_handle_event, wxs); + wxs->unix_source = + wl_event_loop_add_fd(wxs->loop, wxs->unix_fd, + WL_EVENT_READABLE, + weston_xserver_handle_event, wxs); + + wl_global_create(display, &xserver_interface, 1, wxs, bind_xserver); + + wxs->destroy_listener.notify = weston_xserver_destroy; + wl_signal_add(&compositor->destroy_signal, &wxs->destroy_listener); + + return 0; +} diff --git a/src/xwayland/selection.c b/src/xwayland/selection.c new file mode 100644 index 00000000..b694477e --- /dev/null +++ b/src/xwayland/selection.c @@ -0,0 +1,712 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "xwayland.h" + +static int +writable_callback(int fd, uint32_t mask, void *data) +{ + struct weston_wm *wm = data; + unsigned char *property; + int len, remainder; + + property = xcb_get_property_value(wm->property_reply); + remainder = xcb_get_property_value_length(wm->property_reply) - + wm->property_start; + + len = write(fd, property + wm->property_start, remainder); + if (len == -1) { + free(wm->property_reply); + wm->property_reply = NULL; + if (wm->property_source) + wl_event_source_remove(wm->property_source); + close(fd); + weston_log("write error to target fd: %m\n"); + return 1; + } + + weston_log("wrote %d (chunk size %d) of %d bytes\n", + wm->property_start + len, + len, xcb_get_property_value_length(wm->property_reply)); + + wm->property_start += len; + if (len == remainder) { + free(wm->property_reply); + wm->property_reply = NULL; + if (wm->property_source) + wl_event_source_remove(wm->property_source); + + if (wm->incr) { + xcb_delete_property(wm->conn, + wm->selection_window, + wm->atom.wl_selection); + } else { + weston_log("transfer complete\n"); + close(fd); + } + } + + return 1; +} + +static void +weston_wm_write_property(struct weston_wm *wm, xcb_get_property_reply_t *reply) +{ + wm->property_start = 0; + wm->property_reply = reply; + writable_callback(wm->data_source_fd, WL_EVENT_WRITABLE, wm); + + if (wm->property_reply) + wm->property_source = + wl_event_loop_add_fd(wm->server->loop, + wm->data_source_fd, + WL_EVENT_WRITABLE, + writable_callback, wm); +} + +static void +weston_wm_get_incr_chunk(struct weston_wm *wm) +{ + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *reply; + + cookie = xcb_get_property(wm->conn, + 0, /* delete */ + wm->selection_window, + wm->atom.wl_selection, + XCB_GET_PROPERTY_TYPE_ANY, + 0, /* offset */ + 0x1fffffff /* length */); + + reply = xcb_get_property_reply(wm->conn, cookie, NULL); + + dump_property(wm, wm->atom.wl_selection, reply); + + if (xcb_get_property_value_length(reply) > 0) { + weston_wm_write_property(wm, reply); + } else { + weston_log("transfer complete\n"); + close(wm->data_source_fd); + free(reply); + } +} + +struct x11_data_source { + struct weston_data_source base; + struct weston_wm *wm; +}; + +static void +data_source_accept(struct weston_data_source *source, + uint32_t time, const char *mime_type) +{ +} + +static void +data_source_send(struct weston_data_source *base, + const char *mime_type, int32_t fd) +{ + struct x11_data_source *source = (struct x11_data_source *) base; + struct weston_wm *wm = source->wm; + + if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) { + /* Get data for the utf8_string target */ + xcb_convert_selection(wm->conn, + wm->selection_window, + wm->atom.clipboard, + wm->atom.utf8_string, + wm->atom.wl_selection, + XCB_TIME_CURRENT_TIME); + + xcb_flush(wm->conn); + + fcntl(fd, F_SETFL, O_WRONLY | O_NONBLOCK); + wm->data_source_fd = fd; + } +} + +static void +data_source_cancel(struct weston_data_source *source) +{ +} + +static void +weston_wm_get_selection_targets(struct weston_wm *wm) +{ + struct x11_data_source *source; + struct weston_compositor *compositor; + struct weston_seat *seat = weston_wm_pick_seat(wm); + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *reply; + xcb_atom_t *value; + char **p; + uint32_t i; + + cookie = xcb_get_property(wm->conn, + 1, /* delete */ + wm->selection_window, + wm->atom.wl_selection, + XCB_GET_PROPERTY_TYPE_ANY, + 0, /* offset */ + 4096 /* length */); + + reply = xcb_get_property_reply(wm->conn, cookie, NULL); + + dump_property(wm, wm->atom.wl_selection, reply); + + if (reply->type != XCB_ATOM_ATOM) { + free(reply); + return; + } + + source = malloc(sizeof *source); + if (source == NULL) + return; + + wl_signal_init(&source->base.destroy_signal); + source->base.accept = data_source_accept; + source->base.send = data_source_send; + source->base.cancel = data_source_cancel; + source->wm = wm; + + wl_array_init(&source->base.mime_types); + value = xcb_get_property_value(reply); + for (i = 0; i < reply->value_len; i++) { + if (value[i] == wm->atom.utf8_string) { + p = wl_array_add(&source->base.mime_types, sizeof *p); + if (p) + *p = strdup("text/plain;charset=utf-8"); + } + } + + compositor = wm->server->compositor; + weston_seat_set_selection(seat, &source->base, + wl_display_next_serial(compositor->wl_display)); + + free(reply); +} + +static void +weston_wm_get_selection_data(struct weston_wm *wm) +{ + xcb_get_property_cookie_t cookie; + xcb_get_property_reply_t *reply; + + cookie = xcb_get_property(wm->conn, + 1, /* delete */ + wm->selection_window, + wm->atom.wl_selection, + XCB_GET_PROPERTY_TYPE_ANY, + 0, /* offset */ + 0x1fffffff /* length */); + + reply = xcb_get_property_reply(wm->conn, cookie, NULL); + + if (reply->type == wm->atom.incr) { + dump_property(wm, wm->atom.wl_selection, reply); + wm->incr = 1; + free(reply); + } else { + dump_property(wm, wm->atom.wl_selection, reply); + wm->incr = 0; + weston_wm_write_property(wm, reply); + } +} + +static void +weston_wm_handle_selection_notify(struct weston_wm *wm, + xcb_generic_event_t *event) +{ + xcb_selection_notify_event_t *selection_notify = + (xcb_selection_notify_event_t *) event; + + if (selection_notify->property == XCB_ATOM_NONE) { + /* convert selection failed */ + } else if (selection_notify->target == wm->atom.targets) { + weston_wm_get_selection_targets(wm); + } else { + weston_wm_get_selection_data(wm); + } +} + +static const size_t incr_chunk_size = 64 * 1024; + +static void +weston_wm_send_selection_notify(struct weston_wm *wm, xcb_atom_t property) +{ + xcb_selection_notify_event_t selection_notify; + + memset(&selection_notify, 0, sizeof selection_notify); + selection_notify.response_type = XCB_SELECTION_NOTIFY; + selection_notify.sequence = 0; + selection_notify.time = wm->selection_request.time; + selection_notify.requestor = wm->selection_request.requestor; + selection_notify.selection = wm->selection_request.selection; + selection_notify.target = wm->selection_request.target; + selection_notify.property = property; + + xcb_send_event(wm->conn, 0, /* propagate */ + wm->selection_request.requestor, + XCB_EVENT_MASK_NO_EVENT, (char *) &selection_notify); +} + +static void +weston_wm_send_targets(struct weston_wm *wm) +{ + xcb_atom_t targets[] = { + wm->atom.timestamp, + wm->atom.targets, + wm->atom.utf8_string, + /* wm->atom.compound_text, */ + wm->atom.text, + /* wm->atom.string */ + }; + + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + wm->selection_request.requestor, + wm->selection_request.property, + XCB_ATOM_ATOM, + 32, /* format */ + ARRAY_LENGTH(targets), targets); + + weston_wm_send_selection_notify(wm, wm->selection_request.property); +} + +static void +weston_wm_send_timestamp(struct weston_wm *wm) +{ + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + wm->selection_request.requestor, + wm->selection_request.property, + XCB_ATOM_INTEGER, + 32, /* format */ + 1, &wm->selection_timestamp); + + weston_wm_send_selection_notify(wm, wm->selection_request.property); +} + +static int +weston_wm_flush_source_data(struct weston_wm *wm) +{ + int length; + + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + wm->selection_request.requestor, + wm->selection_request.property, + wm->selection_target, + 8, /* format */ + wm->source_data.size, + wm->source_data.data); + wm->selection_property_set = 1; + length = wm->source_data.size; + wm->source_data.size = 0; + + return length; +} + +static int +weston_wm_read_data_source(int fd, uint32_t mask, void *data) +{ + struct weston_wm *wm = data; + int len, current, available; + void *p; + + current = wm->source_data.size; + if (wm->source_data.size < incr_chunk_size) + p = wl_array_add(&wm->source_data, incr_chunk_size); + else + p = (char *) wm->source_data.data + wm->source_data.size; + available = wm->source_data.alloc - current; + + len = read(fd, p, available); + if (len == -1) { + weston_log("read error from data source: %m\n"); + weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); + wl_event_source_remove(wm->property_source); + close(fd); + wl_array_release(&wm->source_data); + } + + weston_log("read %d (available %d, mask 0x%x) bytes: \"%.*s\"\n", + len, available, mask, len, (char *) p); + + wm->source_data.size = current + len; + if (wm->source_data.size >= incr_chunk_size) { + if (!wm->incr) { + weston_log("got %zu bytes, starting incr\n", + wm->source_data.size); + wm->incr = 1; + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + wm->selection_request.requestor, + wm->selection_request.property, + wm->atom.incr, + 32, /* format */ + 1, &incr_chunk_size); + wm->selection_property_set = 1; + wm->flush_property_on_delete = 1; + wl_event_source_remove(wm->property_source); + weston_wm_send_selection_notify(wm, wm->selection_request.property); + } else if (wm->selection_property_set) { + weston_log("got %zu bytes, waiting for " + "property delete\n", wm->source_data.size); + + wm->flush_property_on_delete = 1; + wl_event_source_remove(wm->property_source); + } else { + weston_log("got %zu bytes, " + "property deleted, seting new property\n", + wm->source_data.size); + weston_wm_flush_source_data(wm); + } + } else if (len == 0 && !wm->incr) { + weston_log("non-incr transfer complete\n"); + /* Non-incr transfer all done. */ + weston_wm_flush_source_data(wm); + weston_wm_send_selection_notify(wm, wm->selection_request.property); + xcb_flush(wm->conn); + wl_event_source_remove(wm->property_source); + close(fd); + wl_array_release(&wm->source_data); + wm->selection_request.requestor = XCB_NONE; + } else if (len == 0 && wm->incr) { + weston_log("incr transfer complete\n"); + + wm->flush_property_on_delete = 1; + if (wm->selection_property_set) { + weston_log("got %zu bytes, waiting for " + "property delete\n", wm->source_data.size); + } else { + weston_log("got %zu bytes, " + "property deleted, seting new property\n", + wm->source_data.size); + weston_wm_flush_source_data(wm); + } + xcb_flush(wm->conn); + wl_event_source_remove(wm->property_source); + close(wm->data_source_fd); + wm->data_source_fd = -1; + close(fd); + } else { + weston_log("nothing happened, buffered the bytes\n"); + } + + return 1; +} + +static void +weston_wm_send_data(struct weston_wm *wm, xcb_atom_t target, const char *mime_type) +{ + struct weston_data_source *source; + struct weston_seat *seat = weston_wm_pick_seat(wm); + int p[2]; + + if (pipe2(p, O_CLOEXEC | O_NONBLOCK) == -1) { + weston_log("pipe2 failed: %m\n"); + weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); + return; + } + + wl_array_init(&wm->source_data); + wm->selection_target = target; + wm->data_source_fd = p[0]; + wm->property_source = wl_event_loop_add_fd(wm->server->loop, + wm->data_source_fd, + WL_EVENT_READABLE, + weston_wm_read_data_source, + wm); + + source = seat->selection_data_source; + source->send(source, mime_type, p[1]); + close(p[1]); +} + +static void +weston_wm_send_incr_chunk(struct weston_wm *wm) +{ + int length; + + weston_log("property deleted\n"); + + wm->selection_property_set = 0; + if (wm->flush_property_on_delete) { + weston_log("setting new property, %zu bytes\n", + wm->source_data.size); + wm->flush_property_on_delete = 0; + length = weston_wm_flush_source_data(wm); + + if (wm->data_source_fd >= 0) { + wm->property_source = + wl_event_loop_add_fd(wm->server->loop, + wm->data_source_fd, + WL_EVENT_READABLE, + weston_wm_read_data_source, + wm); + } else if (length > 0) { + /* Transfer is all done, but queue a flush for + * the delete of the last chunk so we can set + * the 0 sized propert to signal the end of + * the transfer. */ + wm->flush_property_on_delete = 1; + wl_array_release(&wm->source_data); + } else { + wm->selection_request.requestor = XCB_NONE; + } + } +} + +static int +weston_wm_handle_selection_property_notify(struct weston_wm *wm, + xcb_generic_event_t *event) +{ + xcb_property_notify_event_t *property_notify = + (xcb_property_notify_event_t *) event; + + if (property_notify->window == wm->selection_window) { + if (property_notify->state == XCB_PROPERTY_NEW_VALUE && + property_notify->atom == wm->atom.wl_selection && + wm->incr) + weston_wm_get_incr_chunk(wm); + return 1; + } else if (property_notify->window == wm->selection_request.requestor) { + if (property_notify->state == XCB_PROPERTY_DELETE && + property_notify->atom == wm->selection_request.property && + wm->incr) + weston_wm_send_incr_chunk(wm); + return 1; + } + + return 0; +} + +static void +weston_wm_handle_selection_request(struct weston_wm *wm, + xcb_generic_event_t *event) +{ + xcb_selection_request_event_t *selection_request = + (xcb_selection_request_event_t *) event; + + weston_log("selection request, %s, ", + get_atom_name(wm->conn, selection_request->selection)); + weston_log_continue("target %s, ", + get_atom_name(wm->conn, selection_request->target)); + weston_log_continue("property %s\n", + get_atom_name(wm->conn, selection_request->property)); + + wm->selection_request = *selection_request; + wm->incr = 0; + wm->flush_property_on_delete = 0; + + if (selection_request->selection == wm->atom.clipboard_manager) { + /* The weston clipboard should already have grabbed + * the first target, so just send selection notify + * now. This isn't synchronized with the clipboard + * finishing getting the data, so there's a race here. */ + weston_wm_send_selection_notify(wm, wm->selection_request.property); + return; + } + + if (selection_request->target == wm->atom.targets) { + weston_wm_send_targets(wm); + } else if (selection_request->target == wm->atom.timestamp) { + weston_wm_send_timestamp(wm); + } else if (selection_request->target == wm->atom.utf8_string || + selection_request->target == wm->atom.text) { + weston_wm_send_data(wm, wm->atom.utf8_string, + "text/plain;charset=utf-8"); + } else { + weston_log("can only handle UTF8_STRING targets...\n"); + weston_wm_send_selection_notify(wm, XCB_ATOM_NONE); + } +} + +static int +weston_wm_handle_xfixes_selection_notify(struct weston_wm *wm, + xcb_generic_event_t *event) +{ + xcb_xfixes_selection_notify_event_t *xfixes_selection_notify = + (xcb_xfixes_selection_notify_event_t *) event; + struct weston_compositor *compositor; + struct weston_seat *seat = weston_wm_pick_seat(wm); + uint32_t serial; + + if (xfixes_selection_notify->selection != wm->atom.clipboard) + return 0; + + weston_log("xfixes selection notify event: owner %d\n", + xfixes_selection_notify->owner); + + if (xfixes_selection_notify->owner == XCB_WINDOW_NONE) { + if (wm->selection_owner != wm->selection_window) { + /* A real X client selection went away, not our + * proxy selection. Clear the wayland selection. */ + compositor = wm->server->compositor; + serial = wl_display_next_serial(compositor->wl_display); + weston_seat_set_selection(seat, NULL, serial); + } + + wm->selection_owner = XCB_WINDOW_NONE; + + return 1; + } + + wm->selection_owner = xfixes_selection_notify->owner; + + /* We have to use XCB_TIME_CURRENT_TIME when we claim the + * selection, so grab the actual timestamp here so we can + * answer TIMESTAMP conversion requests correctly. */ + if (xfixes_selection_notify->owner == wm->selection_window) { + wm->selection_timestamp = xfixes_selection_notify->timestamp; + weston_log("our window, skipping\n"); + return 1; + } + + wm->incr = 0; + xcb_convert_selection(wm->conn, wm->selection_window, + wm->atom.clipboard, + wm->atom.targets, + wm->atom.wl_selection, + xfixes_selection_notify->timestamp); + + xcb_flush(wm->conn); + + return 1; +} + +int +weston_wm_handle_selection_event(struct weston_wm *wm, + xcb_generic_event_t *event) +{ + switch (event->response_type & ~0x80) { + case XCB_SELECTION_NOTIFY: + weston_wm_handle_selection_notify(wm, event); + return 1; + case XCB_PROPERTY_NOTIFY: + return weston_wm_handle_selection_property_notify(wm, event); + case XCB_SELECTION_REQUEST: + weston_wm_handle_selection_request(wm, event); + return 1; + } + + switch (event->response_type - wm->xfixes->first_event) { + case XCB_XFIXES_SELECTION_NOTIFY: + return weston_wm_handle_xfixes_selection_notify(wm, event); + } + + return 0; +} + +static void +weston_wm_set_selection(struct wl_listener *listener, void *data) +{ + struct weston_seat *seat = data; + struct weston_wm *wm = + container_of(listener, struct weston_wm, selection_listener); + struct weston_data_source *source = seat->selection_data_source; + const char **p, **end; + int has_text_plain = 0; + + if (source == NULL) { + if (wm->selection_owner == wm->selection_window) + xcb_set_selection_owner(wm->conn, + XCB_ATOM_NONE, + wm->atom.clipboard, + wm->selection_timestamp); + return; + } + + if (source->send == data_source_send) + return; + + p = source->mime_types.data; + end = (const char **) + ((char *) source->mime_types.data + source->mime_types.size); + while (p < end) { + weston_log(" %s\n", *p); + if (strcmp(*p, "text/plain") == 0 || + strcmp(*p, "text/plain;charset=utf-8") == 0) + has_text_plain = 1; + p++; + } + + if (has_text_plain) { + xcb_set_selection_owner(wm->conn, + wm->selection_window, + wm->atom.clipboard, + XCB_TIME_CURRENT_TIME); + } else { + xcb_set_selection_owner(wm->conn, + XCB_ATOM_NONE, + wm->atom.clipboard, + XCB_TIME_CURRENT_TIME); + } +} + +void +weston_wm_selection_init(struct weston_wm *wm) +{ + struct weston_seat *seat; + uint32_t values[1], mask; + + wm->selection_request.requestor = XCB_NONE; + + values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; + wm->selection_window = xcb_generate_id(wm->conn); + xcb_create_window(wm->conn, + XCB_COPY_FROM_PARENT, + wm->selection_window, + wm->screen->root, + 0, 0, + 10, 10, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + wm->screen->root_visual, + XCB_CW_EVENT_MASK, values); + + xcb_set_selection_owner(wm->conn, + wm->selection_window, + wm->atom.clipboard_manager, + XCB_TIME_CURRENT_TIME); + + mask = + XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY | + XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE; + xcb_xfixes_select_selection_input(wm->conn, wm->selection_window, + wm->atom.clipboard, mask); + + seat = weston_wm_pick_seat(wm); + wm->selection_listener.notify = weston_wm_set_selection; + wl_signal_add(&seat->selection_signal, &wm->selection_listener); + + weston_wm_set_selection(&wm->selection_listener, seat); +} diff --git a/src/xwayland/window-manager.c b/src/xwayland/window-manager.c new file mode 100644 index 00000000..f1b58c9b --- /dev/null +++ b/src/xwayland/window-manager.c @@ -0,0 +1,2186 @@ +/* + * Copyright © 2011 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xwayland.h" + +#include "../../shared/cairo-util.h" +#include "../compositor.h" +#include "xserver-server-protocol.h" +#include "hash.h" + +struct wm_size_hints { + uint32_t flags; + int32_t x, y; + int32_t width, height; /* should set so old wm's don't mess up */ + int32_t min_width, min_height; + int32_t max_width, max_height; + int32_t width_inc, height_inc; + struct { + int32_t x; + int32_t y; + } min_aspect, max_aspect; + int32_t base_width, base_height; + int32_t win_gravity; +}; + +#define USPosition (1L << 0) +#define USSize (1L << 1) +#define PPosition (1L << 2) +#define PSize (1L << 3) +#define PMinSize (1L << 4) +#define PMaxSize (1L << 5) +#define PResizeInc (1L << 6) +#define PAspect (1L << 7) +#define PBaseSize (1L << 8) +#define PWinGravity (1L << 9) + +struct motif_wm_hints { + uint32_t flags; + uint32_t functions; + uint32_t decorations; + int32_t input_mode; + uint32_t status; +}; + +#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_FUNC_ALL (1L << 0) +#define MWM_FUNC_RESIZE (1L << 1) +#define MWM_FUNC_MOVE (1L << 2) +#define MWM_FUNC_MINIMIZE (1L << 3) +#define MWM_FUNC_MAXIMIZE (1L << 4) +#define MWM_FUNC_CLOSE (1L << 5) + +#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) + +#define MWM_INPUT_MODELESS 0 +#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 +#define MWM_INPUT_SYSTEM_MODAL 2 +#define MWM_INPUT_FULL_APPLICATION_MODAL 3 +#define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL + +#define MWM_TEAROFF_WINDOW (1L<<0) + +#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 /* movement only */ +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ +#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ + +struct weston_wm_window { + struct weston_wm *wm; + xcb_window_t id; + xcb_window_t frame_id; + cairo_surface_t *cairo_surface; + struct weston_surface *surface; + struct shell_surface *shsurf; + struct wl_listener surface_destroy_listener; + struct wl_event_source *repaint_source; + struct wl_event_source *configure_source; + int properties_dirty; + int pid; + char *machine; + char *class; + char *name; + struct weston_wm_window *transient_for; + uint32_t protocols; + xcb_atom_t type; + int width, height; + int x, y; + int saved_width, saved_height; + int decorate; + int override_redirect; + int fullscreen; + int has_alpha; + struct wm_size_hints size_hints; + struct motif_wm_hints motif_hints; +}; + +static struct weston_wm_window * +get_wm_window(struct weston_surface *surface); + +static void +weston_wm_window_schedule_repaint(struct weston_wm_window *window); + +static int __attribute__ ((format (printf, 1, 2))) +wm_log(const char *fmt, ...) +{ +#ifdef WM_DEBUG + int l; + va_list argp; + + va_start(argp, fmt); + l = weston_vlog(fmt, argp); + va_end(argp); + + return l; +#else + return 0; +#endif +} + +static int __attribute__ ((format (printf, 1, 2))) +wm_log_continue(const char *fmt, ...) +{ +#ifdef WM_DEBUG + int l; + va_list argp; + + va_start(argp, fmt); + l = weston_vlog_continue(fmt, argp); + va_end(argp); + + return l; +#else + return 0; +#endif +} + + +const char * +get_atom_name(xcb_connection_t *c, xcb_atom_t atom) +{ + xcb_get_atom_name_cookie_t cookie; + xcb_get_atom_name_reply_t *reply; + xcb_generic_error_t *e; + static char buffer[64]; + + if (atom == XCB_ATOM_NONE) + return "None"; + + cookie = xcb_get_atom_name (c, atom); + reply = xcb_get_atom_name_reply (c, cookie, &e); + + if(reply) { + snprintf(buffer, sizeof buffer, "%.*s", + xcb_get_atom_name_name_length (reply), + xcb_get_atom_name_name (reply)); + } else { + snprintf(buffer, sizeof buffer, "(atom %u)", atom); + } + + free(reply); + + return buffer; +} + +static xcb_cursor_t +xcb_cursor_image_load_cursor(struct weston_wm *wm, const XcursorImage *img) +{ + xcb_connection_t *c = wm->conn; + xcb_screen_iterator_t s = xcb_setup_roots_iterator(xcb_get_setup(c)); + xcb_screen_t *screen = s.data; + xcb_gcontext_t gc; + xcb_pixmap_t pix; + xcb_render_picture_t pic; + xcb_cursor_t cursor; + int stride = img->width * 4; + + pix = xcb_generate_id(c); + xcb_create_pixmap(c, 32, pix, screen->root, img->width, img->height); + + pic = xcb_generate_id(c); + xcb_render_create_picture(c, pic, pix, wm->format_rgba.id, 0, 0); + + gc = xcb_generate_id(c); + xcb_create_gc(c, gc, pix, 0, 0); + + xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, + img->width, img->height, 0, 0, 0, 32, + stride * img->height, (uint8_t *) img->pixels); + xcb_free_gc(c, gc); + + cursor = xcb_generate_id(c); + xcb_render_create_cursor(c, cursor, pic, img->xhot, img->yhot); + + xcb_render_free_picture(c, pic); + xcb_free_pixmap(c, pix); + + return cursor; +} + +static xcb_cursor_t +xcb_cursor_images_load_cursor(struct weston_wm *wm, const XcursorImages *images) +{ + /* TODO: treat animated cursors as well */ + if (images->nimage != 1) + return -1; + + return xcb_cursor_image_load_cursor(wm, images->images[0]); +} + +static xcb_cursor_t +xcb_cursor_library_load_cursor(struct weston_wm *wm, const char *file) +{ + xcb_cursor_t cursor; + XcursorImages *images; + char *v = NULL; + int size = 0; + + if (!file) + return 0; + + v = getenv ("XCURSOR_SIZE"); + if (v) + size = atoi(v); + + if (!size) + size = 32; + + images = XcursorLibraryLoadImages (file, NULL, size); + if (!images) + return -1; + + cursor = xcb_cursor_images_load_cursor (wm, images); + XcursorImagesDestroy (images); + + return cursor; +} + +void +dump_property(struct weston_wm *wm, + xcb_atom_t property, xcb_get_property_reply_t *reply) +{ + int32_t *incr_value; + const char *text_value, *name; + xcb_atom_t *atom_value; + int width, len; + uint32_t i; + + width = wm_log_continue("%s: ", get_atom_name(wm->conn, property)); + if (reply == NULL) { + wm_log_continue("(no reply)\n"); + return; + } + + width += wm_log_continue("%s/%d, length %d (value_len %d): ", + get_atom_name(wm->conn, reply->type), + reply->format, + xcb_get_property_value_length(reply), + reply->value_len); + + if (reply->type == wm->atom.incr) { + incr_value = xcb_get_property_value(reply); + wm_log_continue("%d\n", *incr_value); + } else if (reply->type == wm->atom.utf8_string || + reply->type == wm->atom.string) { + text_value = xcb_get_property_value(reply); + if (reply->value_len > 40) + len = 40; + else + len = reply->value_len; + wm_log_continue("\"%.*s\"\n", len, text_value); + } else if (reply->type == XCB_ATOM_ATOM) { + atom_value = xcb_get_property_value(reply); + for (i = 0; i < reply->value_len; i++) { + name = get_atom_name(wm->conn, atom_value[i]); + if (width + strlen(name) + 2 > 78) { + wm_log_continue("\n "); + width = 4; + } else if (i > 0) { + width += wm_log_continue(", "); + } + + width += wm_log_continue("%s", name); + } + wm_log_continue("\n"); + } else { + wm_log_continue("huh?\n"); + } +} + +static void +read_and_dump_property(struct weston_wm *wm, + xcb_window_t window, xcb_atom_t property) +{ + xcb_get_property_reply_t *reply; + xcb_get_property_cookie_t cookie; + + cookie = xcb_get_property(wm->conn, 0, window, + property, XCB_ATOM_ANY, 0, 2048); + reply = xcb_get_property_reply(wm->conn, cookie, NULL); + + dump_property(wm, property, reply); + + free(reply); +} + +/* We reuse some predefined, but otherwise useles atoms */ +#define TYPE_WM_PROTOCOLS XCB_ATOM_CUT_BUFFER0 +#define TYPE_MOTIF_WM_HINTS XCB_ATOM_CUT_BUFFER1 +#define TYPE_NET_WM_STATE XCB_ATOM_CUT_BUFFER2 +#define TYPE_WM_NORMAL_HINTS XCB_ATOM_CUT_BUFFER3 + +static void +weston_wm_window_read_properties(struct weston_wm_window *window) +{ + struct weston_wm *wm = window->wm; + struct weston_shell_interface *shell_interface = + &wm->server->compositor->shell_interface; + +#define F(field) offsetof(struct weston_wm_window, field) + const struct { + xcb_atom_t atom; + xcb_atom_t type; + int offset; + } props[] = { + { XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, F(class) }, + { XCB_ATOM_WM_NAME, XCB_ATOM_STRING, F(name) }, + { XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, F(transient_for) }, + { wm->atom.wm_protocols, TYPE_WM_PROTOCOLS, F(protocols) }, + { wm->atom.wm_normal_hints, TYPE_WM_NORMAL_HINTS, F(protocols) }, + { wm->atom.net_wm_state, TYPE_NET_WM_STATE }, + { wm->atom.net_wm_window_type, XCB_ATOM_ATOM, F(type) }, + { wm->atom.net_wm_name, XCB_ATOM_STRING, F(name) }, + { wm->atom.net_wm_pid, XCB_ATOM_CARDINAL, F(pid) }, + { wm->atom.motif_wm_hints, TYPE_MOTIF_WM_HINTS, 0 }, + { wm->atom.wm_client_machine, XCB_ATOM_WM_CLIENT_MACHINE, F(machine) }, + }; +#undef F + + xcb_get_property_cookie_t cookie[ARRAY_LENGTH(props)]; + xcb_get_property_reply_t *reply; + void *p; + uint32_t *xid; + xcb_atom_t *atom; + uint32_t i; + + if (!window->properties_dirty) + return; + window->properties_dirty = 0; + + for (i = 0; i < ARRAY_LENGTH(props); i++) + cookie[i] = xcb_get_property(wm->conn, + 0, /* delete */ + window->id, + props[i].atom, + XCB_ATOM_ANY, 0, 2048); + + window->decorate = !window->override_redirect; + window->size_hints.flags = 0; + window->motif_hints.flags = 0; + + for (i = 0; i < ARRAY_LENGTH(props); i++) { + reply = xcb_get_property_reply(wm->conn, cookie[i], NULL); + if (!reply) + /* Bad window, typically */ + continue; + if (reply->type == XCB_ATOM_NONE) { + /* No such property */ + free(reply); + continue; + } + + p = ((char *) window + props[i].offset); + + switch (props[i].type) { + case XCB_ATOM_WM_CLIENT_MACHINE: + case XCB_ATOM_STRING: + /* FIXME: We're using this for both string and + utf8_string */ + if (*(char **) p) + free(*(char **) p); + + *(char **) p = + strndup(xcb_get_property_value(reply), + xcb_get_property_value_length(reply)); + break; + case XCB_ATOM_WINDOW: + xid = xcb_get_property_value(reply); + *(struct weston_wm_window **) p = + hash_table_lookup(wm->window_hash, *xid); + break; + case XCB_ATOM_CARDINAL: + case XCB_ATOM_ATOM: + atom = xcb_get_property_value(reply); + *(xcb_atom_t *) p = *atom; + break; + case TYPE_WM_PROTOCOLS: + break; + case TYPE_WM_NORMAL_HINTS: + memcpy(&window->size_hints, + xcb_get_property_value(reply), + sizeof window->size_hints); + break; + case TYPE_NET_WM_STATE: + window->fullscreen = 0; + atom = xcb_get_property_value(reply); + for (i = 0; i < reply->value_len; i++) + if (atom[i] == wm->atom.net_wm_state_fullscreen) + window->fullscreen = 1; + break; + case TYPE_MOTIF_WM_HINTS: + memcpy(&window->motif_hints, + xcb_get_property_value(reply), + sizeof window->motif_hints); + if (window->motif_hints.flags & MWM_HINTS_DECORATIONS) + window->decorate = + window->motif_hints.decorations > 0; + break; + default: + break; + } + free(reply); + } + + if (window->shsurf && window->name) + shell_interface->set_title(window->shsurf, window->name); +} + +static void +weston_wm_window_get_frame_size(struct weston_wm_window *window, + int *width, int *height) +{ + struct theme *t = window->wm->theme; + + if (window->fullscreen) { + *width = window->width; + *height = window->height; + } else if (window->decorate) { + *width = window->width + (t->margin + t->width) * 2; + *height = window->height + + t->margin * 2 + t->width + t->titlebar_height; + } else { + *width = window->width + t->margin * 2; + *height = window->height + t->margin * 2; + } +} + +static void +weston_wm_window_get_child_position(struct weston_wm_window *window, + int *x, int *y) +{ + struct theme *t = window->wm->theme; + + if (window->fullscreen) { + *x = 0; + *y = 0; + } else if (window->decorate) { + *x = t->margin + t->width; + *y = t->margin + t->titlebar_height; + } else { + *x = t->margin; + *y = t->margin; + } +} + +static void +weston_wm_window_send_configure_notify(struct weston_wm_window *window) +{ + xcb_configure_notify_event_t configure_notify; + struct weston_wm *wm = window->wm; + int x, y; + + weston_wm_window_get_child_position(window, &x, &y); + configure_notify.response_type = XCB_CONFIGURE_NOTIFY; + configure_notify.pad0 = 0; + configure_notify.event = window->id; + configure_notify.window = window->id; + configure_notify.above_sibling = XCB_WINDOW_NONE; + configure_notify.x = x; + configure_notify.y = y; + configure_notify.width = window->width; + configure_notify.height = window->height; + configure_notify.border_width = 0; + configure_notify.override_redirect = 0; + configure_notify.pad1 = 0; + + xcb_send_event(wm->conn, 0, window->id, + XCB_EVENT_MASK_STRUCTURE_NOTIFY, + (char *) &configure_notify); +} + +static void +weston_wm_handle_configure_request(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_configure_request_event_t *configure_request = + (xcb_configure_request_event_t *) event; + struct weston_wm_window *window; + uint32_t mask, values[16]; + int x, y, width, height, i = 0; + + wm_log("XCB_CONFIGURE_REQUEST (window %d) %d,%d @ %dx%d\n", + configure_request->window, + configure_request->x, configure_request->y, + configure_request->width, configure_request->height); + + window = hash_table_lookup(wm->window_hash, configure_request->window); + + if (window->fullscreen) { + weston_wm_window_send_configure_notify(window); + return; + } + + if (configure_request->value_mask & XCB_CONFIG_WINDOW_WIDTH) + window->width = configure_request->width; + if (configure_request->value_mask & XCB_CONFIG_WINDOW_HEIGHT) + window->height = configure_request->height; + + weston_wm_window_get_child_position(window, &x, &y); + values[i++] = x; + values[i++] = y; + values[i++] = window->width; + values[i++] = window->height; + values[i++] = 0; + mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT | + XCB_CONFIG_WINDOW_BORDER_WIDTH; + if (configure_request->value_mask & XCB_CONFIG_WINDOW_SIBLING) { + values[i++] = configure_request->sibling; + mask |= XCB_CONFIG_WINDOW_SIBLING; + } + if (configure_request->value_mask & XCB_CONFIG_WINDOW_STACK_MODE) { + values[i++] = configure_request->stack_mode; + mask |= XCB_CONFIG_WINDOW_STACK_MODE; + } + + xcb_configure_window(wm->conn, window->id, mask, values); + + weston_wm_window_get_frame_size(window, &width, &height); + values[0] = width; + values[1] = height; + mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT; + xcb_configure_window(wm->conn, window->frame_id, mask, values); + + weston_wm_window_schedule_repaint(window); +} + +static int +our_resource(struct weston_wm *wm, uint32_t id) +{ + const xcb_setup_t *setup; + + setup = xcb_get_setup(wm->conn); + + return (id & ~setup->resource_id_mask) == setup->resource_id_base; +} + +static void +weston_wm_handle_configure_notify(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_configure_notify_event_t *configure_notify = + (xcb_configure_notify_event_t *) event; + struct weston_wm_window *window; + + wm_log("XCB_CONFIGURE_NOTIFY (window %d) %d,%d @ %dx%d\n", + configure_notify->window, + configure_notify->x, configure_notify->y, + configure_notify->width, configure_notify->height); + + window = hash_table_lookup(wm->window_hash, configure_notify->window); + window->x = configure_notify->x; + window->y = configure_notify->y; + if (window->override_redirect) { + window->width = configure_notify->width; + window->height = configure_notify->height; + } +} + +static void +weston_wm_kill_client(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = data; + struct weston_wm_window *window = get_wm_window(surface); + char name[1024]; + + if (!window) + return; + + gethostname(name, 1024); + + /* this is only one heuristic to guess the PID of a client is valid, + * assuming it's compliant with icccm and ewmh. Non-compliants and + * remote applications of course fail. */ + if (!strcmp(window->machine, name) && window->pid != 0) + kill(window->pid, SIGKILL); +} + +static void +weston_wm_window_activate(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = data; + struct weston_wm_window *window = NULL; + struct weston_wm *wm = + container_of(listener, struct weston_wm, activate_listener); + xcb_client_message_event_t client_message; + + if (surface) { + window = get_wm_window(surface); + } + + if (window) { + client_message.response_type = XCB_CLIENT_MESSAGE; + client_message.format = 32; + client_message.window = window->id; + client_message.type = wm->atom.wm_protocols; + client_message.data.data32[0] = wm->atom.wm_take_focus; + client_message.data.data32[1] = XCB_TIME_CURRENT_TIME; + + xcb_send_event(wm->conn, 0, window->id, + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + (char *) &client_message); + + xcb_set_input_focus (wm->conn, XCB_INPUT_FOCUS_POINTER_ROOT, + window->id, XCB_TIME_CURRENT_TIME); + } else { + xcb_set_input_focus (wm->conn, + XCB_INPUT_FOCUS_POINTER_ROOT, + XCB_NONE, + XCB_TIME_CURRENT_TIME); + } + + if (wm->focus_window) + weston_wm_window_schedule_repaint(wm->focus_window); + wm->focus_window = window; + if (wm->focus_window) + weston_wm_window_schedule_repaint(wm->focus_window); +} + +static void +weston_wm_window_transform(struct wl_listener *listener, void *data) +{ + struct weston_surface *surface = data; + struct weston_wm_window *window = get_wm_window(surface); + struct weston_wm *wm = + container_of(listener, struct weston_wm, transform_listener); + uint32_t mask, values[2]; + + if (!window || !wm) + return; + + if (!weston_surface_is_mapped(surface)) + return; + + if (window->x != surface->geometry.x || + window->y != surface->geometry.y) { + values[0] = surface->geometry.x; + values[1] = surface->geometry.y; + mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y; + + xcb_configure_window(wm->conn, window->frame_id, mask, values); + xcb_flush(wm->conn); + } +} + +#define ICCCM_WITHDRAWN_STATE 0 +#define ICCCM_NORMAL_STATE 1 +#define ICCCM_ICONIC_STATE 3 + +static void +weston_wm_window_set_wm_state(struct weston_wm_window *window, int32_t state) +{ + struct weston_wm *wm = window->wm; + uint32_t property[2]; + + property[0] = state; + property[1] = XCB_WINDOW_NONE; + + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + window->id, + wm->atom.wm_state, + wm->atom.wm_state, + 32, /* format */ + 2, property); +} + +static void +weston_wm_window_set_net_wm_state(struct weston_wm_window *window) +{ + struct weston_wm *wm = window->wm; + uint32_t property[1]; + int i; + + i = 0; + if (window->fullscreen) + property[i++] = wm->atom.net_wm_state_fullscreen; + + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + window->id, + wm->atom.net_wm_state, + XCB_ATOM_ATOM, + 32, /* format */ + i, property); +} + +static void +weston_wm_window_create_frame(struct weston_wm_window *window) +{ + struct weston_wm *wm = window->wm; + uint32_t values[3]; + int x, y, width, height; + + weston_wm_window_get_frame_size(window, &width, &height); + weston_wm_window_get_child_position(window, &x, &y); + + values[0] = wm->screen->black_pixel; + values[1] = + XCB_EVENT_MASK_KEY_PRESS | + XCB_EVENT_MASK_KEY_RELEASE | + XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_BUTTON_RELEASE | + XCB_EVENT_MASK_POINTER_MOTION | + XCB_EVENT_MASK_ENTER_WINDOW | + XCB_EVENT_MASK_LEAVE_WINDOW | + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; + values[2] = wm->colormap; + + window->frame_id = xcb_generate_id(wm->conn); + xcb_create_window(wm->conn, + 32, + window->frame_id, + wm->screen->root, + 0, 0, + width, height, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + wm->visual_id, + XCB_CW_BORDER_PIXEL | + XCB_CW_EVENT_MASK | + XCB_CW_COLORMAP, values); + + xcb_reparent_window(wm->conn, window->id, window->frame_id, x, y); + + values[0] = 0; + xcb_configure_window(wm->conn, window->id, + XCB_CONFIG_WINDOW_BORDER_WIDTH, values); + + window->cairo_surface = + cairo_xcb_surface_create_with_xrender_format(wm->conn, + wm->screen, + window->frame_id, + &wm->format_rgba, + width, height); + + hash_table_insert(wm->window_hash, window->frame_id, window); +} + +static void +weston_wm_handle_map_request(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_map_request_event_t *map_request = + (xcb_map_request_event_t *) event; + struct weston_wm_window *window; + + if (our_resource(wm, map_request->window)) { + wm_log("XCB_MAP_REQUEST (window %d, ours)\n", + map_request->window); + return; + } + + window = hash_table_lookup(wm->window_hash, map_request->window); + + weston_wm_window_read_properties(window); + + if (window->frame_id == XCB_WINDOW_NONE) + weston_wm_window_create_frame(window); + + wm_log("XCB_MAP_REQUEST (window %d, %p, frame %d)\n", + window->id, window, window->frame_id); + + weston_wm_window_set_wm_state(window, ICCCM_NORMAL_STATE); + weston_wm_window_set_net_wm_state(window); + + xcb_map_window(wm->conn, map_request->window); + xcb_map_window(wm->conn, window->frame_id); +} + +static void +weston_wm_handle_map_notify(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_map_notify_event_t *map_notify = (xcb_map_notify_event_t *) event; + + if (our_resource(wm, map_notify->window)) { + wm_log("XCB_MAP_NOTIFY (window %d, ours)\n", + map_notify->window); + return; + } + + wm_log("XCB_MAP_NOTIFY (window %d)\n", map_notify->window); +} + +static void +weston_wm_handle_unmap_notify(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_unmap_notify_event_t *unmap_notify = + (xcb_unmap_notify_event_t *) event; + struct weston_wm_window *window; + + wm_log("XCB_UNMAP_NOTIFY (window %d, event %d%s)\n", + unmap_notify->window, + unmap_notify->event, + our_resource(wm, unmap_notify->window) ? ", ours" : ""); + + if (our_resource(wm, unmap_notify->window)) + return; + + if (unmap_notify->response_type & SEND_EVENT_MASK) + /* We just ignore the ICCCM 4.1.4 synthetic unmap notify + * as it may come in after we've destroyed the window. */ + return; + + window = hash_table_lookup(wm->window_hash, unmap_notify->window); + if (wm->focus_window == window) + wm->focus_window = NULL; + if (window->surface) + wl_list_remove(&window->surface_destroy_listener.link); + window->surface = NULL; + window->shsurf = NULL; + xcb_unmap_window(wm->conn, window->frame_id); +} + +static void +weston_wm_window_draw_decoration(void *data) +{ + struct weston_wm_window *window = data; + struct weston_wm *wm = window->wm; + struct theme *t = wm->theme; + cairo_t *cr; + int x, y, width, height; + const char *title; + uint32_t flags = 0; + + weston_wm_window_read_properties(window); + + window->repaint_source = NULL; + + weston_wm_window_get_frame_size(window, &width, &height); + weston_wm_window_get_child_position(window, &x, &y); + + cairo_xcb_surface_set_size(window->cairo_surface, width, height); + cr = cairo_create(window->cairo_surface); + + if (window->fullscreen) { + /* nothing */ + } else if (window->decorate) { + if (wm->focus_window == window) + flags |= THEME_FRAME_ACTIVE; + + if (window->name) + title = window->name; + else + title = "untitled"; + + theme_render_frame(t, cr, width, height, title, flags); + } else { + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 0, 0, 0, 0); + cairo_paint(cr); + + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba(cr, 0, 0, 0, 0.45); + tile_mask(cr, t->shadow, 2, 2, width + 8, height + 8, 64, 64); + } + + cairo_destroy(cr); + + if (window->surface) { + pixman_region32_fini(&window->surface->pending.opaque); + if(window->has_alpha) { + pixman_region32_init(&window->surface->pending.opaque); + } else { + /* We leave an extra pixel around the X window area to + * make sure we don't sample from the undefined alpha + * channel when filtering. */ + pixman_region32_init_rect(&window->surface->pending.opaque, + x - 1, y - 1, + window->width + 2, + window->height + 2); + } + weston_surface_geometry_dirty(window->surface); + + pixman_region32_fini(&window->surface->pending.input); + + if (window->fullscreen) { + pixman_region32_init_rect(&window->surface->pending.input, + 0, 0, window->width, window->height); + } else if (window->decorate) { + pixman_region32_init_rect(&window->surface->pending.input, + t->margin, t->margin, + width - 2 * t->margin, + height - 2 * t->margin); + } + } +} + +static void +weston_wm_window_schedule_repaint(struct weston_wm_window *window) +{ + struct weston_wm *wm = window->wm; + int width, height; + + if (window->frame_id == XCB_WINDOW_NONE) { + if (window->surface != NULL) { + weston_wm_window_get_frame_size(window, &width, &height); + pixman_region32_fini(&window->surface->pending.opaque); + if(window->has_alpha) { + pixman_region32_init(&window->surface->pending.opaque); + } else { + pixman_region32_init_rect(&window->surface->pending.opaque, 0, 0, + width, height); + } + weston_surface_geometry_dirty(window->surface); + } + return; + } + + if (window->repaint_source) + return; + + window->repaint_source = + wl_event_loop_add_idle(wm->server->loop, + weston_wm_window_draw_decoration, + window); +} + +static void +weston_wm_handle_property_notify(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_property_notify_event_t *property_notify = + (xcb_property_notify_event_t *) event; + struct weston_wm_window *window; + + window = hash_table_lookup(wm->window_hash, property_notify->window); + if (!window) + return; + + window->properties_dirty = 1; + + wm_log("XCB_PROPERTY_NOTIFY: window %d, ", property_notify->window); + if (property_notify->state == XCB_PROPERTY_DELETE) + wm_log("deleted\n"); + else + read_and_dump_property(wm, property_notify->window, + property_notify->atom); + + if (property_notify->atom == wm->atom.net_wm_name || + property_notify->atom == XCB_ATOM_WM_NAME) + weston_wm_window_schedule_repaint(window); +} + +static void +weston_wm_window_create(struct weston_wm *wm, + xcb_window_t id, int width, int height, int x, int y, int override) +{ + struct weston_wm_window *window; + uint32_t values[1]; + xcb_get_geometry_cookie_t geometry_cookie; + xcb_get_geometry_reply_t *geometry_reply; + + window = zalloc(sizeof *window); + if (window == NULL) { + wm_log("failed to allocate window\n"); + return; + } + + geometry_cookie = xcb_get_geometry(wm->conn, id); + + values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(wm->conn, id, XCB_CW_EVENT_MASK, values); + + window->wm = wm; + window->id = id; + window->properties_dirty = 1; + window->override_redirect = override; + window->width = width; + window->height = height; + window->x = x; + window->y = y; + + geometry_reply = xcb_get_geometry_reply(wm->conn, geometry_cookie, NULL); + /* technically we should use XRender and check the visual format's + alpha_mask, but checking depth is simpler and works in all known cases */ + if(geometry_reply != NULL) + window->has_alpha = geometry_reply->depth == 32; + free(geometry_reply); + + hash_table_insert(wm->window_hash, id, window); +} + +static void +weston_wm_window_destroy(struct weston_wm_window *window) +{ + struct weston_wm *wm = window->wm; + + if (window->repaint_source) + wl_event_source_remove(window->repaint_source); + if (window->cairo_surface) + cairo_surface_destroy(window->cairo_surface); + + if (window->frame_id) { + xcb_reparent_window(wm->conn, window->id, wm->wm_window, 0, 0); + xcb_destroy_window(wm->conn, window->frame_id); + weston_wm_window_set_wm_state(window, ICCCM_WITHDRAWN_STATE); + hash_table_remove(wm->window_hash, window->frame_id); + window->frame_id = XCB_WINDOW_NONE; + } + + hash_table_remove(window->wm->window_hash, window->id); + free(window); +} + +static void +weston_wm_handle_create_notify(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_create_notify_event_t *create_notify = + (xcb_create_notify_event_t *) event; + + wm_log("XCB_CREATE_NOTIFY (window %d, width %d, height %d%s%s)\n", + create_notify->window, + create_notify->width, create_notify->height, + create_notify->override_redirect ? ", override" : "", + our_resource(wm, create_notify->window) ? ", ours" : ""); + + if (our_resource(wm, create_notify->window)) + return; + + weston_wm_window_create(wm, create_notify->window, + create_notify->width, create_notify->height, + create_notify->x, create_notify->y, + create_notify->override_redirect); +} + +static void +weston_wm_handle_destroy_notify(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_destroy_notify_event_t *destroy_notify = + (xcb_destroy_notify_event_t *) event; + struct weston_wm_window *window; + + wm_log("XCB_DESTROY_NOTIFY, win %d, event %d%s\n", + destroy_notify->window, + destroy_notify->event, + our_resource(wm, destroy_notify->window) ? ", ours" : ""); + + if (our_resource(wm, destroy_notify->window)) + return; + + window = hash_table_lookup(wm->window_hash, destroy_notify->window); + weston_wm_window_destroy(window); +} + +static void +weston_wm_handle_reparent_notify(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_reparent_notify_event_t *reparent_notify = + (xcb_reparent_notify_event_t *) event; + struct weston_wm_window *window; + + wm_log("XCB_REPARENT_NOTIFY (window %d, parent %d, event %d)\n", + reparent_notify->window, + reparent_notify->parent, + reparent_notify->event); + + if (reparent_notify->parent == wm->screen->root) { + weston_wm_window_create(wm, reparent_notify->window, 10, 10, + reparent_notify->x, reparent_notify->y, + reparent_notify->override_redirect); + } else if (!our_resource(wm, reparent_notify->parent)) { + window = hash_table_lookup(wm->window_hash, + reparent_notify->window); + weston_wm_window_destroy(window); + } +} + +struct weston_seat * +weston_wm_pick_seat(struct weston_wm *wm) +{ + return container_of(wm->server->compositor->seat_list.next, + struct weston_seat, link); +} + +static void +weston_wm_window_handle_moveresize(struct weston_wm_window *window, + xcb_client_message_event_t *client_message) +{ + static const int map[] = { + THEME_LOCATION_RESIZING_TOP_LEFT, + THEME_LOCATION_RESIZING_TOP, + THEME_LOCATION_RESIZING_TOP_RIGHT, + THEME_LOCATION_RESIZING_RIGHT, + THEME_LOCATION_RESIZING_BOTTOM_RIGHT, + THEME_LOCATION_RESIZING_BOTTOM, + THEME_LOCATION_RESIZING_BOTTOM_LEFT, + THEME_LOCATION_RESIZING_LEFT + }; + + struct weston_wm *wm = window->wm; + struct weston_seat *seat = weston_wm_pick_seat(wm); + int detail; + struct weston_shell_interface *shell_interface = + &wm->server->compositor->shell_interface; + + if (seat->pointer->button_count != 1 || + seat->pointer->focus != window->surface) + return; + + detail = client_message->data.data32[2]; + switch (detail) { + case _NET_WM_MOVERESIZE_MOVE: + shell_interface->move(window->shsurf, seat); + break; + case _NET_WM_MOVERESIZE_SIZE_TOPLEFT: + case _NET_WM_MOVERESIZE_SIZE_TOP: + case _NET_WM_MOVERESIZE_SIZE_TOPRIGHT: + case _NET_WM_MOVERESIZE_SIZE_RIGHT: + case _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT: + case _NET_WM_MOVERESIZE_SIZE_BOTTOM: + case _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT: + case _NET_WM_MOVERESIZE_SIZE_LEFT: + shell_interface->resize(window->shsurf, seat, map[detail]); + break; + case _NET_WM_MOVERESIZE_CANCEL: + break; + } +} + +#define _NET_WM_STATE_REMOVE 0 +#define _NET_WM_STATE_ADD 1 +#define _NET_WM_STATE_TOGGLE 2 + +static int +update_state(int action, int *state) +{ + int new_state, changed; + + switch (action) { + case _NET_WM_STATE_REMOVE: + new_state = 0; + break; + case _NET_WM_STATE_ADD: + new_state = 1; + break; + case _NET_WM_STATE_TOGGLE: + new_state = !*state; + break; + default: + return 0; + } + + changed = (*state != new_state); + *state = new_state; + + return changed; +} + +static void +weston_wm_window_configure(void *data); + +static void +weston_wm_window_handle_state(struct weston_wm_window *window, + xcb_client_message_event_t *client_message) +{ + struct weston_wm *wm = window->wm; + struct weston_shell_interface *shell_interface = + &wm->server->compositor->shell_interface; + uint32_t action, property; + + action = client_message->data.data32[0]; + property = client_message->data.data32[1]; + + if (property == wm->atom.net_wm_state_fullscreen && + update_state(action, &window->fullscreen)) { + weston_wm_window_set_net_wm_state(window); + if (window->fullscreen) { + window->saved_width = window->width; + window->saved_height = window->height; + + if (window->shsurf) + shell_interface->set_fullscreen(window->shsurf, + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, + 0, NULL); + } else { + shell_interface->set_toplevel(window->shsurf); + window->width = window->saved_width; + window->height = window->saved_height; + weston_wm_window_configure(window); + } + } +} + +static void +weston_wm_handle_client_message(struct weston_wm *wm, + xcb_generic_event_t *event) +{ + xcb_client_message_event_t *client_message = + (xcb_client_message_event_t *) event; + struct weston_wm_window *window; + + window = hash_table_lookup(wm->window_hash, client_message->window); + + wm_log("XCB_CLIENT_MESSAGE (%s %d %d %d %d %d win %d)\n", + get_atom_name(wm->conn, client_message->type), + client_message->data.data32[0], + client_message->data.data32[1], + client_message->data.data32[2], + client_message->data.data32[3], + client_message->data.data32[4], + client_message->window); + + if (client_message->type == wm->atom.net_wm_moveresize) + weston_wm_window_handle_moveresize(window, client_message); + else if (client_message->type == wm->atom.net_wm_state) + weston_wm_window_handle_state(window, client_message); +} + +enum cursor_type { + XWM_CURSOR_TOP, + XWM_CURSOR_BOTTOM, + XWM_CURSOR_LEFT, + XWM_CURSOR_RIGHT, + XWM_CURSOR_TOP_LEFT, + XWM_CURSOR_TOP_RIGHT, + XWM_CURSOR_BOTTOM_LEFT, + XWM_CURSOR_BOTTOM_RIGHT, + XWM_CURSOR_LEFT_PTR, +}; + +/* + * The following correspondences between file names and cursors was copied + * from: https://bugs.kde.org/attachment.cgi?id=67313 + */ + +static const char *bottom_left_corners[] = { + "bottom_left_corner", + "sw-resize", + "size_bdiag" +}; + +static const char *bottom_right_corners[] = { + "bottom_right_corner", + "se-resize", + "size_fdiag" +}; + +static const char *bottom_sides[] = { + "bottom_side", + "s-resize", + "size_ver" +}; + +static const char *left_ptrs[] = { + "left_ptr", + "default", + "top_left_arrow", + "left-arrow" +}; + +static const char *left_sides[] = { + "left_side", + "w-resize", + "size_hor" +}; + +static const char *right_sides[] = { + "right_side", + "e-resize", + "size_hor" +}; + +static const char *top_left_corners[] = { + "top_left_corner", + "nw-resize", + "size_fdiag" +}; + +static const char *top_right_corners[] = { + "top_right_corner", + "ne-resize", + "size_bdiag" +}; + +static const char *top_sides[] = { + "top_side", + "n-resize", + "size_ver" +}; + +struct cursor_alternatives { + const char **names; + size_t count; +}; + +static const struct cursor_alternatives cursors[] = { + {top_sides, ARRAY_LENGTH(top_sides)}, + {bottom_sides, ARRAY_LENGTH(bottom_sides)}, + {left_sides, ARRAY_LENGTH(left_sides)}, + {right_sides, ARRAY_LENGTH(right_sides)}, + {top_left_corners, ARRAY_LENGTH(top_left_corners)}, + {top_right_corners, ARRAY_LENGTH(top_right_corners)}, + {bottom_left_corners, ARRAY_LENGTH(bottom_left_corners)}, + {bottom_right_corners, ARRAY_LENGTH(bottom_right_corners)}, + {left_ptrs, ARRAY_LENGTH(left_ptrs)}, +}; + +static void +weston_wm_create_cursors(struct weston_wm *wm) +{ + const char *name; + int i, count = ARRAY_LENGTH(cursors); + size_t j; + + wm->cursors = malloc(count * sizeof(xcb_cursor_t)); + for (i = 0; i < count; i++) { + for (j = 0; j < cursors[i].count; j++) { + name = cursors[i].names[j]; + wm->cursors[i] = + xcb_cursor_library_load_cursor(wm, name); + if (wm->cursors[i] != (xcb_cursor_t)-1) + break; + } + } + + wm->last_cursor = -1; +} + +static void +weston_wm_destroy_cursors(struct weston_wm *wm) +{ + uint8_t i; + + for (i = 0; i < ARRAY_LENGTH(cursors); i++) + xcb_free_cursor(wm->conn, wm->cursors[i]); + + free(wm->cursors); +} + +static int +get_cursor_for_location(struct theme *t, int width, int height, int x, int y) +{ + int location = theme_get_location(t, x, y, width, height, 0); + + switch (location) { + case THEME_LOCATION_RESIZING_TOP: + return XWM_CURSOR_TOP; + case THEME_LOCATION_RESIZING_BOTTOM: + return XWM_CURSOR_BOTTOM; + case THEME_LOCATION_RESIZING_LEFT: + return XWM_CURSOR_LEFT; + case THEME_LOCATION_RESIZING_RIGHT: + return XWM_CURSOR_RIGHT; + case THEME_LOCATION_RESIZING_TOP_LEFT: + return XWM_CURSOR_TOP_LEFT; + case THEME_LOCATION_RESIZING_TOP_RIGHT: + return XWM_CURSOR_TOP_RIGHT; + case THEME_LOCATION_RESIZING_BOTTOM_LEFT: + return XWM_CURSOR_BOTTOM_LEFT; + case THEME_LOCATION_RESIZING_BOTTOM_RIGHT: + return XWM_CURSOR_BOTTOM_RIGHT; + case THEME_LOCATION_EXTERIOR: + case THEME_LOCATION_TITLEBAR: + default: + return XWM_CURSOR_LEFT_PTR; + } +} + +static void +weston_wm_window_set_cursor(struct weston_wm *wm, xcb_window_t window_id, + int cursor) +{ + uint32_t cursor_value_list; + + if (wm->last_cursor == cursor) + return; + + wm->last_cursor = cursor; + + cursor_value_list = wm->cursors[cursor]; + xcb_change_window_attributes (wm->conn, window_id, + XCB_CW_CURSOR, &cursor_value_list); + xcb_flush(wm->conn); +} + +static void +weston_wm_handle_button(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_button_press_event_t *button = (xcb_button_press_event_t *) event; + struct weston_shell_interface *shell_interface = + &wm->server->compositor->shell_interface; + struct weston_seat *seat = weston_wm_pick_seat(wm); + struct weston_wm_window *window; + enum theme_location location; + struct theme *t = wm->theme; + int width, height; + + wm_log("XCB_BUTTON_%s (detail %d)\n", + button->response_type == XCB_BUTTON_PRESS ? + "PRESS" : "RELEASE", button->detail); + + window = hash_table_lookup(wm->window_hash, button->event); + weston_wm_window_get_frame_size(window, &width, &height); + + if (button->response_type == XCB_BUTTON_PRESS && + button->detail == 1) { + location = theme_get_location(t, + button->event_x, + button->event_y, + width, height, 0); + + switch (location) { + case THEME_LOCATION_TITLEBAR: + shell_interface->move(window->shsurf, seat); + break; + case THEME_LOCATION_RESIZING_TOP: + case THEME_LOCATION_RESIZING_BOTTOM: + case THEME_LOCATION_RESIZING_LEFT: + case THEME_LOCATION_RESIZING_RIGHT: + case THEME_LOCATION_RESIZING_TOP_LEFT: + case THEME_LOCATION_RESIZING_TOP_RIGHT: + case THEME_LOCATION_RESIZING_BOTTOM_LEFT: + case THEME_LOCATION_RESIZING_BOTTOM_RIGHT: + shell_interface->resize(window->shsurf, + seat, location); + break; + default: + break; + } + } +} + +static void +weston_wm_handle_motion(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_motion_notify_event_t *motion = (xcb_motion_notify_event_t *) event; + struct weston_wm_window *window; + int cursor, width, height; + + window = hash_table_lookup(wm->window_hash, motion->event); + if (!window || !window->decorate) + return; + + weston_wm_window_get_frame_size(window, &width, &height); + cursor = get_cursor_for_location(wm->theme, width, height, + motion->event_x, motion->event_y); + + weston_wm_window_set_cursor(wm, window->frame_id, cursor); +} + +static void +weston_wm_handle_enter(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_enter_notify_event_t *enter = (xcb_enter_notify_event_t *) event; + struct weston_wm_window *window; + int cursor, width, height; + + window = hash_table_lookup(wm->window_hash, enter->event); + if (!window || !window->decorate) + return; + + weston_wm_window_get_frame_size(window, &width, &height); + cursor = get_cursor_for_location(wm->theme, width, height, + enter->event_x, enter->event_y); + + weston_wm_window_set_cursor(wm, window->frame_id, cursor); +} + +static void +weston_wm_handle_leave(struct weston_wm *wm, xcb_generic_event_t *event) +{ + xcb_leave_notify_event_t *leave = (xcb_leave_notify_event_t *) event; + struct weston_wm_window *window; + + window = hash_table_lookup(wm->window_hash, leave->event); + if (!window || !window->decorate) + return; + + weston_wm_window_set_cursor(wm, window->frame_id, XWM_CURSOR_LEFT_PTR); +} + +static int +weston_wm_handle_event(int fd, uint32_t mask, void *data) +{ + struct weston_wm *wm = data; + xcb_generic_event_t *event; + int count = 0; + + while (event = xcb_poll_for_event(wm->conn), event != NULL) { + if (weston_wm_handle_selection_event(wm, event)) { + free(event); + count++; + continue; + } + + if (weston_wm_handle_dnd_event(wm, event)) { + free(event); + count++; + continue; + } + + switch (EVENT_TYPE(event)) { + case XCB_BUTTON_PRESS: + case XCB_BUTTON_RELEASE: + weston_wm_handle_button(wm, event); + break; + case XCB_ENTER_NOTIFY: + weston_wm_handle_enter(wm, event); + break; + case XCB_LEAVE_NOTIFY: + weston_wm_handle_leave(wm, event); + break; + case XCB_MOTION_NOTIFY: + weston_wm_handle_motion(wm, event); + break; + case XCB_CREATE_NOTIFY: + weston_wm_handle_create_notify(wm, event); + break; + case XCB_MAP_REQUEST: + weston_wm_handle_map_request(wm, event); + break; + case XCB_MAP_NOTIFY: + weston_wm_handle_map_notify(wm, event); + break; + case XCB_UNMAP_NOTIFY: + weston_wm_handle_unmap_notify(wm, event); + break; + case XCB_REPARENT_NOTIFY: + weston_wm_handle_reparent_notify(wm, event); + break; + case XCB_CONFIGURE_REQUEST: + weston_wm_handle_configure_request(wm, event); + break; + case XCB_CONFIGURE_NOTIFY: + weston_wm_handle_configure_notify(wm, event); + break; + case XCB_DESTROY_NOTIFY: + weston_wm_handle_destroy_notify(wm, event); + break; + case XCB_MAPPING_NOTIFY: + wm_log("XCB_MAPPING_NOTIFY\n"); + break; + case XCB_PROPERTY_NOTIFY: + weston_wm_handle_property_notify(wm, event); + break; + case XCB_CLIENT_MESSAGE: + weston_wm_handle_client_message(wm, event); + break; + } + + free(event); + count++; + } + + xcb_flush(wm->conn); + + return count; +} + +static void +weston_wm_get_visual_and_colormap(struct weston_wm *wm) +{ + xcb_depth_iterator_t d_iter; + xcb_visualtype_iterator_t vt_iter; + xcb_visualtype_t *visualtype; + + d_iter = xcb_screen_allowed_depths_iterator(wm->screen); + visualtype = NULL; + while (d_iter.rem > 0) { + if (d_iter.data->depth == 32) { + vt_iter = xcb_depth_visuals_iterator(d_iter.data); + visualtype = vt_iter.data; + break; + } + + xcb_depth_next(&d_iter); + } + + if (visualtype == NULL) { + weston_log("no 32 bit visualtype\n"); + return; + } + + wm->visual_id = visualtype->visual_id; + wm->colormap = xcb_generate_id(wm->conn); + xcb_create_colormap(wm->conn, XCB_COLORMAP_ALLOC_NONE, + wm->colormap, wm->screen->root, wm->visual_id); +} + +static void +weston_wm_get_resources(struct weston_wm *wm) +{ + +#define F(field) offsetof(struct weston_wm, field) + + static const struct { const char *name; int offset; } atoms[] = { + { "WM_PROTOCOLS", F(atom.wm_protocols) }, + { "WM_NORMAL_HINTS", F(atom.wm_normal_hints) }, + { "WM_TAKE_FOCUS", F(atom.wm_take_focus) }, + { "WM_DELETE_WINDOW", F(atom.wm_delete_window) }, + { "WM_STATE", F(atom.wm_state) }, + { "WM_S0", F(atom.wm_s0) }, + { "WM_CLIENT_MACHINE", F(atom.wm_client_machine) }, + { "_NET_WM_CM_S0", F(atom.net_wm_cm_s0) }, + { "_NET_WM_NAME", F(atom.net_wm_name) }, + { "_NET_WM_PID", F(atom.net_wm_pid) }, + { "_NET_WM_ICON", F(atom.net_wm_icon) }, + { "_NET_WM_STATE", F(atom.net_wm_state) }, + { "_NET_WM_STATE_FULLSCREEN", F(atom.net_wm_state_fullscreen) }, + { "_NET_WM_USER_TIME", F(atom.net_wm_user_time) }, + { "_NET_WM_ICON_NAME", F(atom.net_wm_icon_name) }, + { "_NET_WM_WINDOW_TYPE", F(atom.net_wm_window_type) }, + + { "_NET_WM_WINDOW_TYPE_DESKTOP", F(atom.net_wm_window_type_desktop) }, + { "_NET_WM_WINDOW_TYPE_DOCK", F(atom.net_wm_window_type_dock) }, + { "_NET_WM_WINDOW_TYPE_TOOLBAR", F(atom.net_wm_window_type_toolbar) }, + { "_NET_WM_WINDOW_TYPE_MENU", F(atom.net_wm_window_type_menu) }, + { "_NET_WM_WINDOW_TYPE_UTILITY", F(atom.net_wm_window_type_utility) }, + { "_NET_WM_WINDOW_TYPE_SPLASH", F(atom.net_wm_window_type_splash) }, + { "_NET_WM_WINDOW_TYPE_DIALOG", F(atom.net_wm_window_type_dialog) }, + { "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", F(atom.net_wm_window_type_dropdown) }, + { "_NET_WM_WINDOW_TYPE_POPUP_MENU", F(atom.net_wm_window_type_popup) }, + { "_NET_WM_WINDOW_TYPE_TOOLTIP", F(atom.net_wm_window_type_tooltip) }, + { "_NET_WM_WINDOW_TYPE_NOTIFICATION", F(atom.net_wm_window_type_notification) }, + { "_NET_WM_WINDOW_TYPE_COMBO", F(atom.net_wm_window_type_combo) }, + { "_NET_WM_WINDOW_TYPE_DND", F(atom.net_wm_window_type_dnd) }, + { "_NET_WM_WINDOW_TYPE_NORMAL", F(atom.net_wm_window_type_normal) }, + + { "_NET_WM_MOVERESIZE", F(atom.net_wm_moveresize) }, + { "_NET_SUPPORTING_WM_CHECK", + F(atom.net_supporting_wm_check) }, + { "_NET_SUPPORTED", F(atom.net_supported) }, + { "_MOTIF_WM_HINTS", F(atom.motif_wm_hints) }, + { "CLIPBOARD", F(atom.clipboard) }, + { "CLIPBOARD_MANAGER", F(atom.clipboard_manager) }, + { "TARGETS", F(atom.targets) }, + { "UTF8_STRING", F(atom.utf8_string) }, + { "_WL_SELECTION", F(atom.wl_selection) }, + { "INCR", F(atom.incr) }, + { "TIMESTAMP", F(atom.timestamp) }, + { "MULTIPLE", F(atom.multiple) }, + { "UTF8_STRING" , F(atom.utf8_string) }, + { "COMPOUND_TEXT", F(atom.compound_text) }, + { "TEXT", F(atom.text) }, + { "STRING", F(atom.string) }, + { "text/plain;charset=utf-8", F(atom.text_plain_utf8) }, + { "text/plain", F(atom.text_plain) }, + { "XdndSelection", F(atom.xdnd_selection) }, + { "XdndAware", F(atom.xdnd_aware) }, + { "XdndEnter", F(atom.xdnd_enter) }, + { "XdndLeave", F(atom.xdnd_leave) }, + { "XdndDrop", F(atom.xdnd_drop) }, + { "XdndStatus", F(atom.xdnd_status) }, + { "XdndFinished", F(atom.xdnd_finished) }, + { "XdndTypeList", F(atom.xdnd_type_list) }, + { "XdndActionCopy", F(atom.xdnd_action_copy) } + }; +#undef F + + xcb_xfixes_query_version_cookie_t xfixes_cookie; + xcb_xfixes_query_version_reply_t *xfixes_reply; + xcb_intern_atom_cookie_t cookies[ARRAY_LENGTH(atoms)]; + xcb_intern_atom_reply_t *reply; + xcb_render_query_pict_formats_reply_t *formats_reply; + xcb_render_query_pict_formats_cookie_t formats_cookie; + xcb_render_pictforminfo_t *formats; + uint32_t i; + + xcb_prefetch_extension_data (wm->conn, &xcb_xfixes_id); + xcb_prefetch_extension_data (wm->conn, &xcb_composite_id); + + formats_cookie = xcb_render_query_pict_formats(wm->conn); + + for (i = 0; i < ARRAY_LENGTH(atoms); i++) + cookies[i] = xcb_intern_atom (wm->conn, 0, + strlen(atoms[i].name), + atoms[i].name); + + for (i = 0; i < ARRAY_LENGTH(atoms); i++) { + reply = xcb_intern_atom_reply (wm->conn, cookies[i], NULL); + *(xcb_atom_t *) ((char *) wm + atoms[i].offset) = reply->atom; + free(reply); + } + + wm->xfixes = xcb_get_extension_data(wm->conn, &xcb_xfixes_id); + if (!wm->xfixes || !wm->xfixes->present) + weston_log("xfixes not available\n"); + + xfixes_cookie = xcb_xfixes_query_version(wm->conn, + XCB_XFIXES_MAJOR_VERSION, + XCB_XFIXES_MINOR_VERSION); + xfixes_reply = xcb_xfixes_query_version_reply(wm->conn, + xfixes_cookie, NULL); + + weston_log("xfixes version: %d.%d\n", + xfixes_reply->major_version, xfixes_reply->minor_version); + + free(xfixes_reply); + + formats_reply = xcb_render_query_pict_formats_reply(wm->conn, + formats_cookie, 0); + if (formats_reply == NULL) + return; + + formats = xcb_render_query_pict_formats_formats(formats_reply); + for (i = 0; i < formats_reply->num_formats; i++) { + if (formats[i].direct.red_mask != 0xff && + formats[i].direct.red_shift != 16) + continue; + if (formats[i].type == XCB_RENDER_PICT_TYPE_DIRECT && + formats[i].depth == 24) + wm->format_rgb = formats[i]; + if (formats[i].type == XCB_RENDER_PICT_TYPE_DIRECT && + formats[i].depth == 32 && + formats[i].direct.alpha_mask == 0xff && + formats[i].direct.alpha_shift == 24) + wm->format_rgba = formats[i]; + } + + free(formats_reply); +} + +static void +weston_wm_create_wm_window(struct weston_wm *wm) +{ + static const char name[] = "Weston WM"; + + wm->wm_window = xcb_generate_id(wm->conn); + xcb_create_window(wm->conn, + XCB_COPY_FROM_PARENT, + wm->wm_window, + wm->screen->root, + 0, 0, + 10, 10, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + wm->screen->root_visual, + 0, NULL); + + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + wm->wm_window, + wm->atom.net_supporting_wm_check, + XCB_ATOM_WINDOW, + 32, /* format */ + 1, &wm->wm_window); + + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + wm->wm_window, + wm->atom.net_wm_name, + wm->atom.utf8_string, + 8, /* format */ + strlen(name), name); + + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + wm->screen->root, + wm->atom.net_supporting_wm_check, + XCB_ATOM_WINDOW, + 32, /* format */ + 1, &wm->wm_window); + + /* Claim the WM_S0 selection even though we don't suport + * the --replace functionality. */ + xcb_set_selection_owner(wm->conn, + wm->wm_window, + wm->atom.wm_s0, + XCB_TIME_CURRENT_TIME); + + xcb_set_selection_owner(wm->conn, + wm->wm_window, + wm->atom.net_wm_cm_s0, + XCB_TIME_CURRENT_TIME); +} + +struct weston_wm * +weston_wm_create(struct weston_xserver *wxs) +{ + struct weston_wm *wm; + struct wl_event_loop *loop; + xcb_screen_iterator_t s; + uint32_t values[1]; + int sv[2]; + xcb_atom_t supported[3]; + + wm = zalloc(sizeof *wm); + if (wm == NULL) + return NULL; + + wm->server = wxs; + wm->window_hash = hash_table_create(); + if (wm->window_hash == NULL) { + free(wm); + return NULL; + } + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) { + weston_log("socketpair failed\n"); + hash_table_destroy(wm->window_hash); + free(wm); + return NULL; + } + + xserver_send_client(wxs->resource, sv[1]); + wl_client_flush(wl_resource_get_client(wxs->resource)); + close(sv[1]); + + /* xcb_connect_to_fd takes ownership of the fd. */ + wm->conn = xcb_connect_to_fd(sv[0], NULL); + if (xcb_connection_has_error(wm->conn)) { + weston_log("xcb_connect_to_fd failed\n"); + close(sv[0]); + hash_table_destroy(wm->window_hash); + free(wm); + return NULL; + } + + s = xcb_setup_roots_iterator(xcb_get_setup(wm->conn)); + wm->screen = s.data; + + loop = wl_display_get_event_loop(wxs->wl_display); + wm->source = + wl_event_loop_add_fd(loop, sv[0], + WL_EVENT_READABLE, + weston_wm_handle_event, wm); + wl_event_source_check(wm->source); + + weston_wm_get_resources(wm); + weston_wm_get_visual_and_colormap(wm); + + values[0] = + XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | + XCB_EVENT_MASK_PROPERTY_CHANGE; + xcb_change_window_attributes(wm->conn, wm->screen->root, + XCB_CW_EVENT_MASK, values); + + xcb_composite_redirect_subwindows(wm->conn, wm->screen->root, + XCB_COMPOSITE_REDIRECT_MANUAL); + + wm->theme = theme_create(); + + weston_wm_create_wm_window(wm); + + supported[0] = wm->atom.net_wm_moveresize; + supported[1] = wm->atom.net_wm_state; + supported[2] = wm->atom.net_wm_state_fullscreen; + xcb_change_property(wm->conn, + XCB_PROP_MODE_REPLACE, + wm->screen->root, + wm->atom.net_supported, + XCB_ATOM_ATOM, + 32, /* format */ + ARRAY_LENGTH(supported), supported); + + weston_wm_selection_init(wm); + + weston_wm_dnd_init(wm); + + xcb_flush(wm->conn); + + wm->activate_listener.notify = weston_wm_window_activate; + wl_signal_add(&wxs->compositor->activate_signal, + &wm->activate_listener); + wm->transform_listener.notify = weston_wm_window_transform; + wl_signal_add(&wxs->compositor->transform_signal, + &wm->transform_listener); + wm->kill_listener.notify = weston_wm_kill_client; + wl_signal_add(&wxs->compositor->kill_signal, + &wm->kill_listener); + + weston_wm_create_cursors(wm); + weston_wm_window_set_cursor(wm, wm->screen->root, XWM_CURSOR_LEFT_PTR); + + weston_log("created wm\n"); + + return wm; +} + +void +weston_wm_destroy(struct weston_wm *wm) +{ + /* FIXME: Free windows in hash. */ + hash_table_destroy(wm->window_hash); + weston_wm_destroy_cursors(wm); + xcb_disconnect(wm->conn); + wl_event_source_remove(wm->source); + wl_list_remove(&wm->selection_listener.link); + wl_list_remove(&wm->activate_listener.link); + wl_list_remove(&wm->kill_listener.link); + wl_list_remove(&wm->transform_listener.link); + + free(wm); +} + +static void +surface_destroy(struct wl_listener *listener, void *data) +{ + struct weston_wm_window *window = + container_of(listener, + struct weston_wm_window, surface_destroy_listener); + + wm_log("surface for xid %d destroyed\n", window->id); + + /* This should have been freed by the shell. + Don't try to use it later. */ + window->shsurf = NULL; + window->surface = NULL; +} + +static struct weston_wm_window * +get_wm_window(struct weston_surface *surface) +{ + struct wl_listener *listener; + + listener = wl_signal_get(&surface->destroy_signal, surface_destroy); + if (listener) + return container_of(listener, struct weston_wm_window, + surface_destroy_listener); + + return NULL; +} + +static void +weston_wm_window_configure(void *data) +{ + struct weston_wm_window *window = data; + struct weston_wm *wm = window->wm; + uint32_t values[4]; + int x, y, width, height; + + weston_wm_window_get_child_position(window, &x, &y); + values[0] = x; + values[1] = y; + values[2] = window->width; + values[3] = window->height; + xcb_configure_window(wm->conn, + window->id, + XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, + values); + + weston_wm_window_get_frame_size(window, &width, &height); + values[0] = width; + values[1] = height; + xcb_configure_window(wm->conn, + window->frame_id, + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, + values); + + window->configure_source = NULL; + + weston_wm_window_schedule_repaint(window); +} + +static void +send_configure(struct weston_surface *surface, + uint32_t edges, int32_t width, int32_t height) +{ + struct weston_wm_window *window = get_wm_window(surface); + struct weston_wm *wm = window->wm; + struct theme *t = window->wm->theme; + int vborder, hborder; + + if (window->fullscreen) { + hborder = 0; + vborder = 0; + } else if (window->decorate) { + hborder = 2 * (t->margin + t->width); + vborder = 2 * t->margin + t->titlebar_height + t->width; + } else { + hborder = 2 * t->margin; + vborder = 2 * t->margin; + } + + if (width > hborder) + window->width = width - hborder; + else + window->width = 1; + + if (height > vborder) + window->height = height - vborder; + else + window->height = 1; + + if (window->configure_source) + return; + + window->configure_source = + wl_event_loop_add_idle(wm->server->loop, + weston_wm_window_configure, window); +} + +static const struct weston_shell_client shell_client = { + send_configure +}; + +static int +legacy_fullscreen(struct weston_wm *wm, + struct weston_wm_window *window, + struct weston_output **output_ret) +{ + struct weston_compositor *compositor = wm->server->compositor; + struct weston_output *output; + uint32_t minmax = PMinSize | PMaxSize; + int matching_size; + + /* Heuristics for detecting legacy fullscreen windows... */ + + wl_list_for_each(output, &compositor->output_list, link) { + if (output->x == window->x && + output->y == window->y && + output->width == window->width && + output->height == window->height && + window->override_redirect) { + *output_ret = output; + return 1; + } + + matching_size = 0; + if ((window->size_hints.flags & (USSize |PSize)) && + window->size_hints.width == output->width && + window->size_hints.height == output->height) + matching_size = 1; + if ((window->size_hints.flags & minmax) == minmax && + window->size_hints.min_width == output->width && + window->size_hints.min_height == output->height && + window->size_hints.max_width == output->width && + window->size_hints.max_height == output->height) + matching_size = 1; + + if (matching_size && !window->decorate && + (window->size_hints.flags & (USPosition | PPosition)) && + window->size_hints.x == output->x && + window->size_hints.y == output->y) { + *output_ret = output; + return 1; + } + } + + return 0; +} +static void +xserver_map_shell_surface(struct weston_wm *wm, + struct weston_wm_window *window) +{ + struct weston_shell_interface *shell_interface = + &wm->server->compositor->shell_interface; + struct weston_output *output; + + if (!shell_interface->create_shell_surface) + return; + + window->shsurf = + shell_interface->create_shell_surface(shell_interface->shell, + window->surface, + &shell_client); + + if (window->name) + shell_interface->set_title(window->shsurf, window->name); + + if (window->fullscreen) { + window->saved_width = window->width; + window->saved_height = window->height; + shell_interface->set_fullscreen(window->shsurf, + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, + 0, NULL); + return; + } else if (legacy_fullscreen(wm, window, &output)) { + window->fullscreen = 1; + shell_interface->set_fullscreen(window->shsurf, + WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, + 0, output); + } else if (!window->override_redirect && !window->transient_for) { + shell_interface->set_toplevel(window->shsurf); + return; + } else { + shell_interface->set_xwayland(window->shsurf, + window->x, + window->y, + WL_SHELL_SURFACE_TRANSIENT_INACTIVE); + } +} + +static void +xserver_set_window_id(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *surface_resource, uint32_t id) +{ + struct weston_xserver *wxs = wl_resource_get_user_data(resource); + struct weston_wm *wm = wxs->wm; + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct weston_wm_window *window; + + if (client != wxs->client) + return; + + window = hash_table_lookup(wm->window_hash, id); + if (window == NULL) { + weston_log("set_window_id for unknown window %d\n", id); + return; + } + + wm_log("set_window_id %d for surface %p\n", id, surface); + + weston_wm_window_read_properties(window); + + /* A weston_wm_window may have many different surfaces assigned + * throughout its life, so we must make sure to remove the listener + * from the old surface signal list. */ + if (window->surface) + wl_list_remove(&window->surface_destroy_listener.link); + + window->surface = (struct weston_surface *) surface; + window->surface_destroy_listener.notify = surface_destroy; + wl_signal_add(&surface->destroy_signal, + &window->surface_destroy_listener); + + weston_wm_window_schedule_repaint(window); + xserver_map_shell_surface(wm, window); +} + +const struct xserver_interface xserver_implementation = { + xserver_set_window_id +}; diff --git a/src/xwayland/xwayland.h b/src/xwayland/xwayland.h new file mode 100644 index 00000000..77262e8f --- /dev/null +++ b/src/xwayland/xwayland.h @@ -0,0 +1,175 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "../compositor.h" + +#define SEND_EVENT_MASK (0x80) +#define EVENT_TYPE(event) ((event)->response_type & ~SEND_EVENT_MASK) + +struct weston_xserver { + struct wl_display *wl_display; + struct wl_event_loop *loop; + struct wl_event_source *sigchld_source; + int abstract_fd; + struct wl_event_source *abstract_source; + int unix_fd; + struct wl_event_source *unix_source; + int display; + struct weston_process process; + struct wl_resource *resource; + struct wl_client *client; + struct weston_compositor *compositor; + struct weston_wm *wm; + struct wl_listener destroy_listener; +}; + +struct weston_wm { + xcb_connection_t *conn; + const xcb_query_extension_reply_t *xfixes; + struct wl_event_source *source; + xcb_screen_t *screen; + struct hash_table *window_hash; + struct weston_xserver *server; + xcb_window_t wm_window; + struct weston_wm_window *focus_window; + struct theme *theme; + xcb_cursor_t *cursors; + int last_cursor; + xcb_render_pictforminfo_t format_rgb, format_rgba; + xcb_visualid_t visual_id; + xcb_colormap_t colormap; + struct wl_listener activate_listener; + struct wl_listener transform_listener; + struct wl_listener kill_listener; + + xcb_window_t selection_window; + xcb_window_t selection_owner; + int incr; + int data_source_fd; + struct wl_event_source *property_source; + xcb_get_property_reply_t *property_reply; + int property_start; + struct wl_array source_data; + xcb_selection_request_event_t selection_request; + xcb_atom_t selection_target; + xcb_timestamp_t selection_timestamp; + int selection_property_set; + int flush_property_on_delete; + struct wl_listener selection_listener; + + xcb_window_t dnd_window; + xcb_window_t dnd_owner; + + struct { + xcb_atom_t wm_protocols; + xcb_atom_t wm_normal_hints; + xcb_atom_t wm_take_focus; + xcb_atom_t wm_delete_window; + xcb_atom_t wm_state; + xcb_atom_t wm_s0; + xcb_atom_t wm_client_machine; + xcb_atom_t net_wm_cm_s0; + xcb_atom_t net_wm_name; + xcb_atom_t net_wm_pid; + xcb_atom_t net_wm_icon; + xcb_atom_t net_wm_state; + xcb_atom_t net_wm_state_fullscreen; + xcb_atom_t net_wm_user_time; + xcb_atom_t net_wm_icon_name; + xcb_atom_t net_wm_window_type; + xcb_atom_t net_wm_window_type_desktop; + xcb_atom_t net_wm_window_type_dock; + xcb_atom_t net_wm_window_type_toolbar; + xcb_atom_t net_wm_window_type_menu; + xcb_atom_t net_wm_window_type_utility; + xcb_atom_t net_wm_window_type_splash; + xcb_atom_t net_wm_window_type_dialog; + xcb_atom_t net_wm_window_type_dropdown; + xcb_atom_t net_wm_window_type_popup; + xcb_atom_t net_wm_window_type_tooltip; + xcb_atom_t net_wm_window_type_notification; + xcb_atom_t net_wm_window_type_combo; + xcb_atom_t net_wm_window_type_dnd; + xcb_atom_t net_wm_window_type_normal; + xcb_atom_t net_wm_moveresize; + xcb_atom_t net_supporting_wm_check; + xcb_atom_t net_supported; + xcb_atom_t motif_wm_hints; + xcb_atom_t clipboard; + xcb_atom_t clipboard_manager; + xcb_atom_t targets; + xcb_atom_t utf8_string; + xcb_atom_t wl_selection; + xcb_atom_t incr; + xcb_atom_t timestamp; + xcb_atom_t multiple; + xcb_atom_t compound_text; + xcb_atom_t text; + xcb_atom_t string; + xcb_atom_t text_plain_utf8; + xcb_atom_t text_plain; + xcb_atom_t xdnd_selection; + xcb_atom_t xdnd_aware; + xcb_atom_t xdnd_enter; + xcb_atom_t xdnd_leave; + xcb_atom_t xdnd_drop; + xcb_atom_t xdnd_status; + xcb_atom_t xdnd_finished; + xcb_atom_t xdnd_type_list; + xcb_atom_t xdnd_action_copy; + } atom; +}; + +void +dump_property(struct weston_wm *wm, xcb_atom_t property, + xcb_get_property_reply_t *reply); + +const char * +get_atom_name(xcb_connection_t *c, xcb_atom_t atom); + +void +weston_wm_selection_init(struct weston_wm *wm); +int +weston_wm_handle_selection_event(struct weston_wm *wm, + xcb_generic_event_t *event); + +extern const struct xserver_interface xserver_implementation; + +struct weston_wm * +weston_wm_create(struct weston_xserver *wxs); +void +weston_wm_destroy(struct weston_wm *wm); + +struct weston_seat * +weston_wm_pick_seat(struct weston_wm *wm); + +int +weston_wm_handle_dnd_event(struct weston_wm *wm, + xcb_generic_event_t *event); +void +weston_wm_dnd_init(struct weston_wm *wm); diff --git a/src/zoom.c b/src/zoom.c new file mode 100644 index 00000000..220b2b6e --- /dev/null +++ b/src/zoom.c @@ -0,0 +1,397 @@ +/* + * Copyright © 2012 Scott Moreau + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include + +#include "compositor.h" +#include "text-cursor-position-server-protocol.h" + +struct text_cursor_position { + struct weston_compositor *ec; + struct wl_global *global; + struct wl_listener destroy_listener; +}; + +static void +text_cursor_position_notify(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *surface_resource, + wl_fixed_t x, wl_fixed_t y) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + + weston_text_cursor_position_notify(surface, x, y); +} + +struct text_cursor_position_interface text_cursor_position_implementation = { + text_cursor_position_notify +}; + +static void +bind_text_cursor_position(struct wl_client *client, + void *data, uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + + resource = wl_resource_create(client, + &text_cursor_position_interface, 1, id); + if (resource) + wl_resource_set_implementation(resource, + &text_cursor_position_implementation, + data, NULL); +} + +static void +text_cursor_position_notifier_destroy(struct wl_listener *listener, void *data) +{ + struct text_cursor_position *text_cursor_position = + container_of(listener, struct text_cursor_position, destroy_listener); + + wl_global_destroy(text_cursor_position->global); + free(text_cursor_position); +} + +void +text_cursor_position_notifier_create(struct weston_compositor *ec) +{ + struct text_cursor_position *text_cursor_position; + + text_cursor_position = malloc(sizeof *text_cursor_position); + if (text_cursor_position == NULL) + return; + + text_cursor_position->ec = ec; + + text_cursor_position->global = + wl_global_create(ec->wl_display, + &text_cursor_position_interface, 1, + text_cursor_position, + bind_text_cursor_position); + + text_cursor_position->destroy_listener.notify = + text_cursor_position_notifier_destroy; + wl_signal_add(&ec->destroy_signal, &text_cursor_position->destroy_listener); +} + +WL_EXPORT void +weston_text_cursor_position_notify(struct weston_surface *surface, + wl_fixed_t cur_pos_x, + wl_fixed_t cur_pos_y) +{ + struct weston_output *output; + wl_fixed_t global_x, global_y; + + weston_surface_to_global_fixed(surface, cur_pos_x, cur_pos_y, + &global_x, &global_y); + + wl_list_for_each(output, &surface->compositor->output_list, link) + if (output->zoom.active && + pixman_region32_contains_point(&output->region, + wl_fixed_to_int(global_x), + wl_fixed_to_int(global_y), + NULL)) { + output->zoom.text_cursor.x = global_x; + output->zoom.text_cursor.y = global_y; + weston_output_update_zoom(output, ZOOM_FOCUS_TEXT); + } +} + +static void +weston_zoom_frame_z(struct weston_animation *animation, + struct weston_output *output, uint32_t msecs) +{ + if (animation->frame_counter <= 1) + output->zoom.spring_z.timestamp = msecs; + + weston_spring_update(&output->zoom.spring_z, msecs); + + if (output->zoom.spring_z.current > output->zoom.max_level) + output->zoom.spring_z.current = output->zoom.max_level; + else if (output->zoom.spring_z.current < 0.0) + output->zoom.spring_z.current = 0.0; + + if (weston_spring_done(&output->zoom.spring_z)) { + if (output->zoom.active && output->zoom.level <= 0.0) { + output->zoom.active = 0; + output->disable_planes--; + } + output->zoom.spring_z.current = output->zoom.level; + wl_list_remove(&animation->link); + wl_list_init(&animation->link); + } + + output->dirty = 1; + weston_output_damage(output); +} + +static struct weston_seat * +weston_zoom_pick_seat(struct weston_compositor *compositor) +{ + return container_of(compositor->seat_list.next, + struct weston_seat, link); +} + + +static void +weston_zoom_frame_xy(struct weston_animation *animation, + struct weston_output *output, uint32_t msecs) +{ + struct weston_seat *seat = weston_zoom_pick_seat(output->compositor); + wl_fixed_t x, y; + + if (animation->frame_counter <= 1) + output->zoom.spring_xy.timestamp = msecs; + + weston_spring_update(&output->zoom.spring_xy, msecs); + + x = output->zoom.from.x - ((output->zoom.from.x - output->zoom.to.x) * + output->zoom.spring_xy.current); + y = output->zoom.from.y - ((output->zoom.from.y - output->zoom.to.y) * + output->zoom.spring_xy.current); + + output->zoom.current.x = x; + output->zoom.current.y = y; + + if (weston_spring_done(&output->zoom.spring_xy)) { + output->zoom.spring_xy.current = output->zoom.spring_xy.target; + output->zoom.current.x = + output->zoom.type == ZOOM_FOCUS_POINTER ? + seat->pointer->x : output->zoom.text_cursor.x; + output->zoom.current.y = + output->zoom.type == ZOOM_FOCUS_POINTER ? + seat->pointer->y : output->zoom.text_cursor.y; + wl_list_remove(&animation->link); + wl_list_init(&animation->link); + } + + output->dirty = 1; + weston_output_damage(output); +} + +static void +zoom_area_center_from_pointer(struct weston_output *output, + wl_fixed_t *x, wl_fixed_t *y) +{ + float level = output->zoom.spring_z.current; + wl_fixed_t offset_x = wl_fixed_from_int(output->x); + wl_fixed_t offset_y = wl_fixed_from_int(output->y); + wl_fixed_t w = wl_fixed_from_int(output->width); + wl_fixed_t h = wl_fixed_from_int(output->height); + + *x -= ((((*x - offset_x) / (float) w) - 0.5) * (w * (1.0 - level))); + *y -= ((((*y - offset_y) / (float) h) - 0.5) * (h * (1.0 - level))); +} + +static void +weston_zoom_apply_output_transform(struct weston_output *output, + float *x, float *y) +{ + float tx, ty; + + switch(output->transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + default: + return; + case WL_OUTPUT_TRANSFORM_90: + tx = -*y; + ty = *x; + break; + case WL_OUTPUT_TRANSFORM_180: + tx = -*x; + ty = -*y; + break; + case WL_OUTPUT_TRANSFORM_270: + tx = *y; + ty = -*x; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED: + tx = -*x; + ty = *y; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + tx = -*y; + ty = -*x; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: + tx = *x; + ty = -*y; + break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: + tx = *y; + ty = *x; + break; + } + + *x = tx; + *y = ty; +} + +static void +weston_output_update_zoom_transform(struct weston_output *output) +{ + uint32_t type = output->zoom.type; + float global_x, global_y; + wl_fixed_t x = output->zoom.current.x; + wl_fixed_t y = output->zoom.current.y; + float trans_min, trans_max; + float ratio, level; + + level = output->zoom.spring_z.current; + ratio = 1 / level; + + if (!output->zoom.active || level > output->zoom.max_level || + level == 0.0f) + return; + + if (type == ZOOM_FOCUS_POINTER && + wl_list_empty(&output->zoom.animation_xy.link)) + zoom_area_center_from_pointer(output, &x, &y); + + global_x = wl_fixed_to_double(x); + global_y = wl_fixed_to_double(y); + + output->zoom.trans_x = + ((((global_x - output->x) / output->width) * + (level * 2)) - level) * ratio; + output->zoom.trans_y = + ((((global_y - output->y) / output->height) * + (level * 2)) - level) * ratio; + + weston_zoom_apply_output_transform(output, &output->zoom.trans_x, + &output->zoom.trans_y); + + trans_max = level * 2 - level; + trans_min = -trans_max; + + /* Clip zoom area to output */ + if (output->zoom.trans_x > trans_max) + output->zoom.trans_x = trans_max; + else if (output->zoom.trans_x < trans_min) + output->zoom.trans_x = trans_min; + if (output->zoom.trans_y > trans_max) + output->zoom.trans_y = trans_max; + else if (output->zoom.trans_y < trans_min) + output->zoom.trans_y = trans_min; +} + +static void +weston_zoom_transition(struct weston_output *output, uint32_t type, + wl_fixed_t x, wl_fixed_t y) +{ + if (output->zoom.type != type) { + /* Set from/to points and start animation */ + output->zoom.spring_xy.current = 0.0; + output->zoom.spring_xy.previous = 0.0; + output->zoom.spring_xy.target = 1.0; + + if (wl_list_empty(&output->zoom.animation_xy.link)) { + output->zoom.animation_xy.frame_counter = 0; + wl_list_insert(output->animation_list.prev, + &output->zoom.animation_xy.link); + + output->zoom.from.x = (type == ZOOM_FOCUS_TEXT) ? + x : output->zoom.text_cursor.x; + output->zoom.from.y = (type == ZOOM_FOCUS_TEXT) ? + y : output->zoom.text_cursor.y; + } else { + output->zoom.from.x = output->zoom.current.x; + output->zoom.from.y = output->zoom.current.y; + } + + output->zoom.to.x = (type == ZOOM_FOCUS_POINTER) ? + x : output->zoom.text_cursor.x; + output->zoom.to.y = (type == ZOOM_FOCUS_POINTER) ? + y : output->zoom.text_cursor.y; + output->zoom.current.x = output->zoom.from.x; + output->zoom.current.y = output->zoom.from.y; + + output->zoom.type = type; + } + + if (output->zoom.level != output->zoom.spring_z.current) { + output->zoom.spring_z.target = output->zoom.level; + if (wl_list_empty(&output->zoom.animation_z.link)) { + output->zoom.animation_z.frame_counter = 0; + wl_list_insert(output->animation_list.prev, + &output->zoom.animation_z.link); + } + } + + output->dirty = 1; + weston_output_damage(output); +} + +WL_EXPORT void +weston_output_update_zoom(struct weston_output *output, uint32_t type) +{ + struct weston_seat *seat = weston_zoom_pick_seat(output->compositor); + wl_fixed_t x = seat->pointer->x; + wl_fixed_t y = seat->pointer->y; + + zoom_area_center_from_pointer(output, &x, &y); + + if (type == ZOOM_FOCUS_POINTER) { + if (wl_list_empty(&output->zoom.animation_xy.link)) { + output->zoom.current.x = seat->pointer->x; + output->zoom.current.y = seat->pointer->y; + } else { + output->zoom.to.x = x; + output->zoom.to.y = y; + } + } + + if (type == ZOOM_FOCUS_TEXT) { + if (wl_list_empty(&output->zoom.animation_xy.link)) { + output->zoom.current.x = output->zoom.text_cursor.x; + output->zoom.current.y = output->zoom.text_cursor.y; + } else { + output->zoom.to.x = output->zoom.text_cursor.x; + output->zoom.to.y = output->zoom.text_cursor.y; + } + } + + weston_zoom_transition(output, type, x, y); + weston_output_update_zoom_transform(output); +} + +WL_EXPORT void +weston_output_init_zoom(struct weston_output *output) +{ + output->zoom.active = 0; + output->zoom.increment = 0.07; + output->zoom.max_level = 0.95; + output->zoom.level = 0.0; + output->zoom.trans_x = 0.0; + output->zoom.trans_y = 0.0; + output->zoom.type = ZOOM_FOCUS_POINTER; + weston_spring_init(&output->zoom.spring_z, 250.0, 0.0, 0.0); + output->zoom.spring_z.friction = 1000; + output->zoom.animation_z.frame = weston_zoom_frame_z; + wl_list_init(&output->zoom.animation_z.link); + weston_spring_init(&output->zoom.spring_xy, 250.0, 0.0, 0.0); + output->zoom.spring_xy.friction = 1000; + output->zoom.animation_xy.frame = weston_zoom_frame_xy; + wl_list_init(&output->zoom.animation_xy.link); +} diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..aba378c3 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,13 @@ +*.test +*.trs +*.weston +logs +matrix-test +setbacklight +test-client +test-text-client +wayland-test-client-protocol.h +wayland-test-protocol.c +wayland-test-server-protocol.h +subsurface-client-protocol.h +subsurface-protocol.c diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 00000000..5be52c6e --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,158 @@ +TESTS = $(shared_tests) $(module_tests) $(weston_tests) + +shared_tests = \ + config-parser.test \ + vertex-clip.test + +module_tests = \ + surface-test.la \ + surface-global-test.la + +weston_test = weston-test.la + +weston_tests = \ + keyboard.weston \ + event.weston \ + button.weston \ + text.weston \ + subsurface.weston \ + $(xwayland_test) + +AM_TESTS_ENVIRONMENT = \ + abs_builddir='$(abs_builddir)'; export abs_builddir; + +TEST_EXTENSIONS = .la .weston +LA_LOG_COMPILER = $(srcdir)/weston-tests-env +WESTON_LOG_COMPILER = $(srcdir)/weston-tests-env + +clean-local: + -rm -rf logs + +# To remove when automake 1.11 support is dropped +export abs_builddir + +noinst_LTLIBRARIES = \ + $(weston_test) \ + $(module_tests) + +noinst_PROGRAMS = \ + $(setbacklight) \ + $(shared_tests) \ + $(weston_tests) \ + matrix-test + +AM_CFLAGS = $(GCC_CFLAGS) +AM_CPPFLAGS = \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/shared \ + -I$(top_builddir)/src \ + -DUNIT_TEST \ + $(COMPOSITOR_CFLAGS) + +surface_global_test_la_SOURCES = surface-global-test.c +surface_global_test_la_LDFLAGS = -module -avoid-version -rpath $(libdir) +surface_test_la_SOURCES = surface-test.c +surface_test_la_LDFLAGS = -module -avoid-version -rpath $(libdir) + +weston_test_la_LIBADD = $(COMPOSITOR_LIBS) \ + ../shared/libshared.la +weston_test_la_LDFLAGS = -module -avoid-version -rpath $(libdir) +weston_test_la_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) +weston_test_la_SOURCES = \ + weston-test.c \ + wayland-test-protocol.c \ + wayland-test-server-protocol.h + +weston_test_runner_src = \ + weston-test-runner.c \ + weston-test-runner.h + +check_LTLIBRARIES = libshared-test.la + +libshared_test_la_SOURCES = \ + $(weston_test_runner_src) +libshared_test_la_CFLAGS = $(GCC_CFLAGS) $(COMPOSITOR_CFLAGS) + +config_parser_test_LDADD = \ + ../shared/libshared.la \ + libshared-test.la \ + $(COMPOSITOR_LIBS) +config_parser_test_SOURCES = \ + config-parser-test.c +vertex_clip_test_SOURCES = \ + vertex-clip-test.c \ + ../src/vertex-clipping.c \ + ../src/vertex-clipping.h +vertex_clip_test_LDADD = \ + libshared-test.la \ + -lm -lrt + +weston_test_client_src = \ + weston-test-client-helper.c \ + weston-test-client-helper.h \ + wayland-test-protocol.c \ + wayland-test-client-protocol.h \ + subsurface-protocol.c \ + subsurface-client-protocol.h +weston_test_client_libs = \ + $(SIMPLE_CLIENT_LIBS) \ + ../shared/libshared.la \ + libshared-test.la + +keyboard_weston_SOURCES = keyboard-test.c $(weston_test_client_src) +keyboard_weston_LDADD = $(weston_test_client_libs) + +event_weston_SOURCES = event-test.c $(weston_test_client_src) +event_weston_LDADD = $(weston_test_client_libs) + +button_weston_SOURCES = button-test.c $(weston_test_client_src) +button_weston_LDADD = $(weston_test_client_libs) + +text_weston_SOURCES = \ + text-test.c \ + ../clients/text-protocol.c \ + $(weston_test_client_src) +text_weston_LDADD = $(weston_test_client_libs) + +subsurface_weston_SOURCES = subsurface-test.c $(weston_test_client_src) +subsurface_weston_LDADD = $(weston_test_client_libs) + +xwayland_weston_SOURCES = xwayland-test.c $(weston_test_client_src) + +xwayland_weston_LDADD = $(weston_test_client_libs) $(XWAYLAND_TEST_LIBS) + +if ENABLE_XWAYLAND_TEST +xwayland_test = xwayland.weston +endif + +matrix_test_SOURCES = \ + matrix-test.c \ + $(top_srcdir)/shared/matrix.c \ + $(top_srcdir)/shared/matrix.h +matrix_test_LDADD = -lm -lrt + +setbacklight_SOURCES = \ + setbacklight.c \ + $(top_srcdir)/src/libbacklight.c \ + $(top_srcdir)/src/libbacklight.h + +setbacklight_CFLAGS = $(AM_CFLAGS) $(SETBACKLIGHT_CFLAGS) +setbacklight_LDADD = $(SETBACKLIGHT_LIBS) + +if BUILD_SETBACKLIGHT +setbacklight = setbacklight +endif + +EXTRA_DIST = weston-tests-env + +BUILT_SOURCES = \ + subsurface-protocol.c \ + subsurface-client-protocol.h \ + wayland-test-protocol.c \ + wayland-test-server-protocol.h \ + wayland-test-client-protocol.h + +CLEANFILES = $(BUILT_SOURCES) + +wayland_protocoldir = $(top_srcdir)/protocol +include $(top_srcdir)/wayland-scanner.mk diff --git a/tests/button-test.c b/tests/button-test.c new file mode 100644 index 00000000..dc02fd44 --- /dev/null +++ b/tests/button-test.c @@ -0,0 +1,55 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "weston-test-client-helper.h" + +TEST(simple_button_test) +{ + struct client *client; + struct pointer *pointer; + + client = client_create(100, 100, 100, 100); + assert(client); + + pointer = client->input->pointer; + + assert(pointer->button == 0); + assert(pointer->state == 0); + + wl_test_move_pointer(client->test->wl_test, 150, 150); + client_roundtrip(client); + assert(pointer->x == 50); + assert(pointer->y == 50); + + wl_test_send_button(client->test->wl_test, BTN_LEFT, + WL_POINTER_BUTTON_STATE_PRESSED); + client_roundtrip(client); + assert(pointer->button == BTN_LEFT); + assert(pointer->state == WL_POINTER_BUTTON_STATE_PRESSED); + + wl_test_send_button(client->test->wl_test, BTN_LEFT, + WL_POINTER_BUTTON_STATE_RELEASED); + client_roundtrip(client); + assert(pointer->button == BTN_LEFT); + assert(pointer->state == WL_POINTER_BUTTON_STATE_RELEASED); +} diff --git a/tests/config-parser-test.c b/tests/config-parser-test.c new file mode 100644 index 00000000..4b8fc7e7 --- /dev/null +++ b/tests/config-parser-test.c @@ -0,0 +1,203 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "config-parser.h" + +static struct weston_config * +run_test(const char *text) +{ + struct weston_config *config; + char file[] = "/tmp/weston-config-parser-test-XXXXXX"; + int fd, len; + + fd = mkstemp(file); + len = write(fd, text, strlen(text)); + assert(len == (int) strlen(text)); + + config = weston_config_parse(file); + close(fd); + unlink(file); + + return config; +} + +static const char t0[] = + "# nothing in this file...\n"; + +static const char t1[] = + "# comment line here...\n" + "\n" + "[foo]\n" + "a=b\n" + "name= Roy Batty \n" + "\n" + "\n" + "[bar]\n" + "# more comments\n" + "number=5252\n" + "flag=false\n" + "\n" + "[stuff]\n" + "flag= true \n" + "\n" + "[bucket]\n" + "color=blue \n" + "contents=live crabs\n" + "pinchy=true\n" + "\n" + "[bucket]\n" + "material=plastic \n" + "color=red\n" + "contents=sand\n"; + +static const char *section_names[] = { + "foo", "bar", "stuff", "bucket", "bucket" +}; + +static const char t2[] = + "# invalid section...\n" + "[this bracket isn't closed\n"; + +static const char t3[] = + "# line without = ...\n" + "[bambam]\n" + "this line isn't any kind of valid\n"; + +static const char t4[] = + "# starting with = ...\n" + "[bambam]\n" + "=not valid at all\n"; + +int main(int argc, char *argv[]) +{ + struct weston_config *config; + struct weston_config_section *section; + const char *name; + char *s; + int r, b, i; + int32_t n; + uint32_t u; + + config = run_test(t0); + assert(config); + weston_config_destroy(config); + + config = run_test(t1); + assert(config); + section = weston_config_get_section(config, "mollusc", NULL, NULL); + assert(section == NULL); + + section = weston_config_get_section(config, "foo", NULL, NULL); + r = weston_config_section_get_string(section, "a", &s, NULL); + assert(r == 0 && strcmp(s, "b") == 0); + free(s); + + section = weston_config_get_section(config, "foo", NULL, NULL); + r = weston_config_section_get_string(section, "b", &s, NULL); + assert(r == -1 && errno == ENOENT && s == NULL); + + section = weston_config_get_section(config, "foo", NULL, NULL); + r = weston_config_section_get_string(section, "name", &s, NULL); + assert(r == 0 && strcmp(s, "Roy Batty") == 0); + free(s); + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_string(section, "a", &s, "boo"); + assert(r == -1 && errno == ENOENT && strcmp(s, "boo") == 0); + free(s); + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_int(section, "number", &n, 600); + assert(r == 0 && n == 5252); + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_int(section, "+++", &n, 700); + assert(r == -1 && errno == ENOENT && n == 700); + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_uint(section, "number", &u, 600); + assert(r == 0 && u == 5252); + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_uint(section, "+++", &u, 600); + assert(r == -1 && errno == ENOENT && u == 600); + + section = weston_config_get_section(config, "bar", NULL, NULL); + r = weston_config_section_get_bool(section, "flag", &b, 600); + assert(r == 0 && b == 0); + + section = weston_config_get_section(config, "stuff", NULL, NULL); + r = weston_config_section_get_bool(section, "flag", &b, -1); + assert(r == 0 && b == 1); + + section = weston_config_get_section(config, "stuff", NULL, NULL); + r = weston_config_section_get_bool(section, "bonk", &b, -1); + assert(r == -1 && errno == ENOENT && b == -1); + + section = weston_config_get_section(config, "bucket", "color", "blue"); + r = weston_config_section_get_string(section, "contents", &s, NULL); + assert(r == 0 && strcmp(s, "live crabs") == 0); + free(s); + + section = weston_config_get_section(config, "bucket", "color", "red"); + r = weston_config_section_get_string(section, "contents", &s, NULL); + assert(r == 0 && strcmp(s, "sand") == 0); + free(s); + + section = weston_config_get_section(config, "bucket", "color", "pink"); + assert(section == NULL); + r = weston_config_section_get_string(section, "contents", &s, "eels"); + assert(r == -1 && errno == ENOENT && strcmp(s, "eels") == 0); + free(s); + + section = NULL; + i = 0; + while (weston_config_next_section(config, §ion, &name)) + assert(strcmp(section_names[i++], name) == 0); + assert(i == 5); + + weston_config_destroy(config); + + config = run_test(t2); + assert(config == NULL); + + config = run_test(t3); + assert(config == NULL); + + config = run_test(t4); + assert(config == NULL); + + weston_config_destroy(NULL); + assert(weston_config_next_section(NULL, NULL, NULL) == 0); + + section = weston_config_get_section(NULL, "bucket", NULL, NULL); + assert(section == NULL); + + return 0; +} diff --git a/tests/event-test.c b/tests/event-test.c new file mode 100644 index 00000000..980dfaad --- /dev/null +++ b/tests/event-test.c @@ -0,0 +1,418 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2013 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "weston-test-client-helper.h" + +static void +check_pointer(struct client *client, int x, int y) +{ + int sx, sy; + + /* check that the client got the global pointer update */ + assert(client->test->pointer_x == x); + assert(client->test->pointer_y == y); + + /* Does global pointer map onto the surface? */ + if (surface_contains(client->surface, x, y)) { + /* check that the surface has the pointer focus */ + assert(client->input->pointer->focus == client->surface); + + /* + * check that the local surface pointer maps + * to the global pointer. + */ + sx = client->input->pointer->x + client->surface->x; + sy = client->input->pointer->y + client->surface->y; + assert(sx == x); + assert(sy == y); + } else { + /* + * The global pointer does not map onto surface. So + * check that it doesn't have the pointer focus. + */ + assert(client->input->pointer->focus == NULL); + } +} + +static void +check_pointer_move(struct client *client, int x, int y) +{ + wl_test_move_pointer(client->test->wl_test, x, y); + client_roundtrip(client); + check_pointer(client, x, y); +} + +TEST(test_pointer_top_left) +{ + struct client *client; + int x, y; + + client = client_create(46, 76, 111, 134); + assert(client); + + /* move pointer outside top left */ + x = client->surface->x - 1; + y = client->surface->y - 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on top left */ + x += 1; y += 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside top left */ + x -= 1; y -= 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_bottom_left) +{ + struct client *client; + int x, y; + + client = client_create(99, 100, 100, 98); + assert(client); + + /* move pointer outside bottom left */ + x = client->surface->x - 1; + y = client->surface->y + client->surface->height; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on bottom left */ + x += 1; y -= 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside bottom left */ + x -= 1; y += 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_top_right) +{ + struct client *client; + int x, y; + + client = client_create(48, 100, 67, 100); + assert(client); + + /* move pointer outside top right */ + x = client->surface->x + client->surface->width; + y = client->surface->y - 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on top right */ + x -= 1; y += 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside top right */ + x += 1; y -= 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_bottom_right) +{ + struct client *client; + int x, y; + + client = client_create(100, 123, 100, 69); + assert(client); + + /* move pointer outside bottom right */ + x = client->surface->x + client->surface->width; + y = client->surface->y + client->surface->height; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on bottom right */ + x -= 1; y -= 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside bottom right */ + x += 1; y += 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_top_center) +{ + struct client *client; + int x, y; + + client = client_create(100, 201, 100, 50); + assert(client); + + /* move pointer outside top center */ + x = client->surface->x + client->surface->width/2; + y = client->surface->y - 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on top center */ + y += 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside top center */ + y -= 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_bottom_center) +{ + struct client *client; + int x, y; + + client = client_create(100, 45, 67, 100); + assert(client); + + /* move pointer outside bottom center */ + x = client->surface->x + client->surface->width/2; + y = client->surface->y + client->surface->height; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on bottom center */ + y -= 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside bottom center */ + y += 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_left_center) +{ + struct client *client; + int x, y; + + client = client_create(167, 45, 78, 100); + assert(client); + + /* move pointer outside left center */ + x = client->surface->x - 1; + y = client->surface->y + client->surface->height/2; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on left center */ + x += 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside left center */ + x -= 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_right_center) +{ + struct client *client; + int x, y; + + client = client_create(110, 37, 100, 46); + assert(client); + + /* move pointer outside right center */ + x = client->surface->x + client->surface->width; + y = client->surface->y + client->surface->height/2; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer on right center */ + x -= 1; + assert(surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); + + /* move pointer outside right center */ + x += 1; + assert(!surface_contains(client->surface, x, y)); + check_pointer_move(client, x, y); +} + +TEST(test_pointer_surface_move) +{ + struct client *client; + + client = client_create(100, 100, 100, 100); + assert(client); + + /* move pointer outside of client */ + assert(!surface_contains(client->surface, 50, 50)); + check_pointer_move(client, 50, 50); + + /* move client center to pointer */ + move_client(client, 0, 0); + assert(surface_contains(client->surface, 50, 50)); + check_pointer(client, 50, 50); +} + +static int +output_contains_client(struct client *client) +{ + struct output *output = client->output; + struct surface *surface = client->surface; + + return !(output->x >= surface->x + surface->width + || output->x + output->width <= surface->x + || output->y >= surface->y + surface->height + || output->y + output->height <= surface->y); +} + +static void +check_client_move(struct client *client, int x, int y) +{ + move_client(client, x, y); + + if (output_contains_client(client)) { + assert(client->surface->output == client->output); + } else { + assert(client->surface->output == NULL); + } +} + +TEST(test_surface_output) +{ + struct client *client; + int x, y; + + client = client_create(100, 100, 100, 100); + assert(client); + + assert(output_contains_client(client)); + + /* not visible */ + x = 0; + y = -client->surface->height; + check_client_move(client, x, y); + + /* visible */ + check_client_move(client, x, ++y); + + /* not visible */ + x = -client->surface->width; + y = 0; + check_client_move(client, x, y); + + /* visible */ + check_client_move(client, ++x, y); + + /* not visible */ + x = client->output->width; + y = 0; + check_client_move(client, x, y); + + /* visible */ + check_client_move(client, --x, y); + assert(output_contains_client(client)); + + /* not visible */ + x = 0; + y = client->output->height; + check_client_move(client, x, y); + assert(!output_contains_client(client)); + + /* visible */ + check_client_move(client, x, --y); + assert(output_contains_client(client)); +} + +static void +buffer_release_handler(void *data, struct wl_buffer *buffer) +{ + int *released = data; + + *released = 1; +} + +static struct wl_buffer_listener buffer_listener = { + buffer_release_handler +}; + +TEST(buffer_release) +{ + struct client *client; + struct wl_surface *surface; + struct wl_buffer *buf1; + struct wl_buffer *buf2; + struct wl_buffer *buf3; + int buf1_released = 0; + int buf2_released = 0; + int buf3_released = 0; + int frame; + + client = client_create(100, 100, 100, 100); + assert(client); + surface = client->surface->wl_surface; + + buf1 = create_shm_buffer(client, 100, 100, NULL); + wl_buffer_add_listener(buf1, &buffer_listener, &buf1_released); + + buf2 = create_shm_buffer(client, 100, 100, NULL); + wl_buffer_add_listener(buf2, &buffer_listener, &buf2_released); + + buf3 = create_shm_buffer(client, 100, 100, NULL); + wl_buffer_add_listener(buf3, &buffer_listener, &buf3_released); + + /* + * buf1 must never be released, since it is replaced before + * it is committed, therefore it never becomes busy. + */ + + wl_surface_attach(surface, buf1, 0, 0); + wl_surface_attach(surface, buf2, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + assert(buf1_released == 0); + /* buf2 may or may not be released */ + assert(buf3_released == 0); + + wl_surface_attach(surface, buf3, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + assert(buf1_released == 0); + assert(buf2_released == 1); + /* buf3 may or may not be released */ + + wl_surface_attach(surface, client->surface->wl_buffer, 0, 0); + frame_callback_set(surface, &frame); + wl_surface_commit(surface); + frame_callback_wait(client, &frame); + assert(buf1_released == 0); + assert(buf2_released == 1); + assert(buf3_released == 1); +} diff --git a/tests/keyboard-test.c b/tests/keyboard-test.c new file mode 100644 index 00000000..542bf4eb --- /dev/null +++ b/tests/keyboard-test.c @@ -0,0 +1,65 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "weston-test-client-helper.h" + +TEST(simple_keyboard_test) +{ + struct client *client; + struct surface *expect_focus = NULL; + struct keyboard *keyboard; + uint32_t expect_key = 0; + uint32_t expect_state = 0; + + client = client_create(10, 10, 1, 1); + assert(client); + + keyboard = client->input->keyboard; + + while(1) { + assert(keyboard->key == expect_key); + assert(keyboard->state == expect_state); + assert(keyboard->focus == expect_focus); + + if (keyboard->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + expect_state = WL_KEYBOARD_KEY_STATE_RELEASED; + wl_test_send_key(client->test->wl_test, expect_key, + expect_state); + } else if (keyboard->focus) { + expect_focus = NULL; + wl_test_activate_surface(client->test->wl_test, + NULL); + } else if (expect_key < 10) { + expect_key++; + expect_focus = client->surface; + expect_state = WL_KEYBOARD_KEY_STATE_PRESSED; + wl_test_activate_surface(client->test->wl_test, + expect_focus->wl_surface); + wl_test_send_key(client->test->wl_test, expect_key, + expect_state); + } else { + break; + } + + client_roundtrip(client); + } +} diff --git a/tests/matrix-test.c b/tests/matrix-test.c new file mode 100644 index 00000000..5b0513f3 --- /dev/null +++ b/tests/matrix-test.c @@ -0,0 +1,419 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "../shared/matrix.h" + +struct inverse_matrix { + double LU[16]; /* column-major */ + unsigned perm[4]; /* permutation */ +}; + +static struct timespec begin_time; + +static void +reset_timer(void) +{ + clock_gettime(CLOCK_MONOTONIC, &begin_time); +} + +static double +read_timer(void) +{ + struct timespec t; + + clock_gettime(CLOCK_MONOTONIC, &t); + return (double)(t.tv_sec - begin_time.tv_sec) + + 1e-9 * (t.tv_nsec - begin_time.tv_nsec); +} + +static double +det3x3(const float *c0, const float *c1, const float *c2) +{ + return (double) + c0[0] * c1[1] * c2[2] + + c1[0] * c2[1] * c0[2] + + c2[0] * c0[1] * c1[2] - + c0[2] * c1[1] * c2[0] - + c1[2] * c2[1] * c0[0] - + c2[2] * c0[1] * c1[0]; +} + +static double +determinant(const struct weston_matrix *m) +{ + double det = 0; +#if 1 + /* develop on last row */ + det -= m->d[3 + 0 * 4] * det3x3(&m->d[4], &m->d[8], &m->d[12]); + det += m->d[3 + 1 * 4] * det3x3(&m->d[0], &m->d[8], &m->d[12]); + det -= m->d[3 + 2 * 4] * det3x3(&m->d[0], &m->d[4], &m->d[12]); + det += m->d[3 + 3 * 4] * det3x3(&m->d[0], &m->d[4], &m->d[8]); +#else + /* develop on first row */ + det += m->d[0 + 0 * 4] * det3x3(&m->d[5], &m->d[9], &m->d[13]); + det -= m->d[0 + 1 * 4] * det3x3(&m->d[1], &m->d[9], &m->d[13]); + det += m->d[0 + 2 * 4] * det3x3(&m->d[1], &m->d[5], &m->d[13]); + det -= m->d[0 + 3 * 4] * det3x3(&m->d[1], &m->d[5], &m->d[9]); +#endif + return det; +} + +static void +print_permutation_matrix(const struct inverse_matrix *m) +{ + const unsigned *p = m->perm; + const char *row[4] = { + "1 0 0 0\n", + "0 1 0 0\n", + "0 0 1 0\n", + "0 0 0 1\n" + }; + + printf(" P =\n%s%s%s%s", row[p[0]], row[p[1]], row[p[2]], row[p[3]]); +} + +static void +print_LU_decomposition(const struct inverse_matrix *m) +{ + unsigned r, c; + + printf(" L " + " U\n"); + for (r = 0; r < 4; ++r) { + double v; + + for (c = 0; c < 4; ++c) { + if (c < r) + v = m->LU[r + c * 4]; + else if (c == r) + v = 1.0; + else + v = 0.0; + printf(" %12.6f", v); + } + + printf(" | "); + + for (c = 0; c < 4; ++c) { + if (c >= r) + v = m->LU[r + c * 4]; + else + v = 0.0; + printf(" %12.6f", v); + } + printf("\n"); + } +} + +static void +print_inverse_data_matrix(const struct inverse_matrix *m) +{ + unsigned r, c; + + for (r = 0; r < 4; ++r) { + for (c = 0; c < 4; ++c) + printf(" %12.6f", m->LU[r + c * 4]); + printf("\n"); + } + + printf("permutation: "); + for (r = 0; r < 4; ++r) + printf(" %u", m->perm[r]); + printf("\n"); +} + +static void +print_matrix(const struct weston_matrix *m) +{ + unsigned r, c; + + for (r = 0; r < 4; ++r) { + for (c = 0; c < 4; ++c) + printf(" %14.6e", m->d[r + c * 4]); + printf("\n"); + } +} + +static double +frand(void) +{ + double r = random(); + return r / (double)(RAND_MAX / 2) - 1.0f; +} + +static void +randomize_matrix(struct weston_matrix *m) +{ + unsigned i; + for (i = 0; i < 16; ++i) +#if 1 + m->d[i] = frand() * exp(10.0 * frand()); +#else + m->d[i] = frand(); +#endif +} + +/* Take a matrix, compute inverse, multiply together + * and subtract the identity matrix to get the error matrix. + * Return the largest absolute value from the error matrix. + */ +static double +test_inverse(struct weston_matrix *m) +{ + unsigned i; + struct inverse_matrix q; + double errsup = 0.0; + + if (matrix_invert(q.LU, q.perm, m) != 0) + return INFINITY; + + for (i = 0; i < 4; ++i) + inverse_transform(q.LU, q.perm, &m->d[i * 4]); + + m->d[0] -= 1.0f; + m->d[5] -= 1.0f; + m->d[10] -= 1.0f; + m->d[15] -= 1.0f; + + for (i = 0; i < 16; ++i) { + double err = fabs(m->d[i]); + if (err > errsup) + errsup = err; + } + + return errsup; +} + +enum { + TEST_OK, + TEST_NOT_INVERTIBLE_OK, + TEST_FAIL, + TEST_COUNT +}; + +static int +test(void) +{ + struct weston_matrix m; + double det, errsup; + + randomize_matrix(&m); + det = determinant(&m); + + errsup = test_inverse(&m); + if (errsup < 1e-6) + return TEST_OK; + + if (fabs(det) < 1e-5 && isinf(errsup)) + return TEST_NOT_INVERTIBLE_OK; + + printf("test fail, det: %g, error sup: %g\n", det, errsup); + + return TEST_FAIL; +} + +static int running; +static void +stopme(int n) +{ + running = 0; +} + +static void +test_loop_precision(void) +{ + int counts[TEST_COUNT] = { 0 }; + + printf("\nRunning a test loop for 10 seconds...\n"); + running = 1; + alarm(10); + while (running) { + counts[test()]++; + } + + printf("tests: %d ok, %d not invertible but ok, %d failed.\n" + "Total: %d iterations.\n", + counts[TEST_OK], counts[TEST_NOT_INVERTIBLE_OK], + counts[TEST_FAIL], + counts[TEST_OK] + counts[TEST_NOT_INVERTIBLE_OK] + + counts[TEST_FAIL]); +} + +static void __attribute__((noinline)) +test_loop_speed_matrixvector(void) +{ + struct weston_matrix m; + struct weston_vector v = { { 0.5, 0.5, 0.5, 1.0 } }; + unsigned long count = 0; + double t; + + printf("\nRunning 3 s test on weston_matrix_transform()...\n"); + + weston_matrix_init(&m); + + running = 1; + alarm(3); + reset_timer(); + while (running) { + weston_matrix_transform(&m, &v); + count++; + } + t = read_timer(); + + printf("%lu iterations in %f seconds, avg. %.1f us/iter.\n", + count, t, 1e9 * t / count); +} + +static void __attribute__((noinline)) +test_loop_speed_inversetransform(void) +{ + struct weston_matrix m; + struct inverse_matrix inv; + struct weston_vector v = { { 0.5, 0.5, 0.5, 1.0 } }; + unsigned long count = 0; + double t; + + printf("\nRunning 3 s test on inverse_transform()...\n"); + + weston_matrix_init(&m); + matrix_invert(inv.LU, inv.perm, &m); + + running = 1; + alarm(3); + reset_timer(); + while (running) { + inverse_transform(inv.LU, inv.perm, v.f); + count++; + } + t = read_timer(); + + printf("%lu iterations in %f seconds, avg. %.1f us/iter.\n", + count, t, 1e9 * t / count); +} + +static void __attribute__((noinline)) +test_loop_speed_invert(void) +{ + struct weston_matrix m; + struct inverse_matrix inv; + unsigned long count = 0; + double t; + + printf("\nRunning 3 s test on matrix_invert()...\n"); + + weston_matrix_init(&m); + + running = 1; + alarm(3); + reset_timer(); + while (running) { + matrix_invert(inv.LU, inv.perm, &m); + count++; + } + t = read_timer(); + + printf("%lu iterations in %f seconds, avg. %.1f ns/iter.\n", + count, t, 1e9 * t / count); +} + +static void __attribute__((noinline)) +test_loop_speed_invert_explicit(void) +{ + struct weston_matrix m; + unsigned long count = 0; + double t; + + printf("\nRunning 3 s test on weston_matrix_invert()...\n"); + + weston_matrix_init(&m); + + running = 1; + alarm(3); + reset_timer(); + while (running) { + weston_matrix_invert(&m, &m); + count++; + } + t = read_timer(); + + printf("%lu iterations in %f seconds, avg. %.1f ns/iter.\n", + count, t, 1e9 * t / count); +} + +int main(void) +{ + struct sigaction ding; + struct weston_matrix M; + struct inverse_matrix Q; + int ret; + double errsup; + double det; + + ding.sa_handler = stopme; + sigemptyset(&ding.sa_mask); + ding.sa_flags = 0; + sigaction(SIGALRM, &ding, NULL); + + srandom(13); + + M.d[0] = 3.0; M.d[4] = 17.0; M.d[8] = 10.0; M.d[12] = 0.0; + M.d[1] = 2.0; M.d[5] = 4.0; M.d[9] = -2.0; M.d[13] = 0.0; + M.d[2] = 6.0; M.d[6] = 18.0; M.d[10] = -12; M.d[14] = 0.0; + M.d[3] = 0.0; M.d[7] = 0.0; M.d[11] = 0.0; M.d[15] = 1.0; + + ret = matrix_invert(Q.LU, Q.perm, &M); + printf("ret = %d\n", ret); + printf("det = %g\n\n", determinant(&M)); + + if (ret != 0) + return 1; + + print_inverse_data_matrix(&Q); + printf("P * A = L * U\n"); + print_permutation_matrix(&Q); + print_LU_decomposition(&Q); + + + printf("a random matrix:\n"); + randomize_matrix(&M); + det = determinant(&M); + print_matrix(&M); + errsup = test_inverse(&M); + printf("\nThe matrix multiplied by its inverse, error:\n"); + print_matrix(&M); + printf("max abs error: %g, original determinant %g\n", errsup, det); + + test_loop_precision(); + test_loop_speed_matrixvector(); + test_loop_speed_inversetransform(); + test_loop_speed_invert(); + test_loop_speed_invert_explicit(); + + return 0; +} diff --git a/tests/setbacklight.c b/tests/setbacklight.c new file mode 100644 index 00000000..92bd4bf3 --- /dev/null +++ b/tests/setbacklight.c @@ -0,0 +1,186 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Author: Tiago Vignatti + */ +/* + * \file setbacklight.c + * Test program to get a backlight connector and set its brightness value. + * Queries for the connectors id can be performed using drm/tests/modeprint + * program. + */ + +#include +#include +#include +#include +#include +#include + +#include "libbacklight.h" + +static uint32_t +get_drm_connector_type(struct udev_device *drm_device, uint32_t connector_id) +{ + const char *filename; + int fd, i, connector_type; + drmModeResPtr res; + drmModeConnectorPtr connector; + + filename = udev_device_get_devnode(drm_device); + fd = open(filename, O_RDWR | O_CLOEXEC); + if (fd < 0) { + printf("couldn't open drm_device\n"); + return -1; + } + + res = drmModeGetResources(fd); + if (res == 0) { + printf("Failed to get resources from card\n"); + close(fd); + return -1; + } + + for (i = 0; i < res->count_connectors; i++) { + connector = drmModeGetConnector(fd, res->connectors[i]); + if (!connector) + continue; + + if ((connector->connection == DRM_MODE_DISCONNECTED) || + (connector->connector_id != connector_id)) { + drmModeFreeConnector(connector); + continue; + } + + connector_type = connector->connector_type; + drmModeFreeConnector(connector); + drmModeFreeResources(res); + + close(fd); + return connector_type; + } + + close(fd); + drmModeFreeResources(res); + return -1; +} + +/* returns a value between 0-255 range, where higher is brighter */ +static uint32_t +get_normalized_backlight(struct backlight *backlight) +{ + long brightness, max_brightness; + long norm; + + brightness = backlight_get_brightness(backlight); + max_brightness = backlight_get_max_brightness(backlight); + + /* convert it to a scale of 0 to 255 */ + norm = (brightness * 255)/(max_brightness); + + return (int) norm; +} + +static void +set_backlight(struct udev_device *drm_device, int connector_id, int blight) +{ + int connector_type; + long max_brightness, brightness, actual_brightness; + struct backlight *backlight; + long new_blight; + + connector_type = get_drm_connector_type(drm_device, connector_id); + if (connector_type < 0) + return; + + backlight = backlight_init(drm_device, connector_type); + if (!backlight) { + printf("backlight adjust failed\n"); + return; + } + + max_brightness = backlight_get_max_brightness(backlight); + printf("Max backlight: %ld\n", max_brightness); + + brightness = backlight_get_brightness(backlight); + printf("Cached backlight: %ld\n", brightness); + + actual_brightness = backlight_get_actual_brightness(backlight); + printf("Hardware backlight: %ld\n", actual_brightness); + + printf("normalized current brightness: %d\n", + get_normalized_backlight(backlight)); + + /* denormalized value */ + new_blight = (blight * max_brightness) / 255; + + backlight_set_brightness(backlight, new_blight); + printf("Setting brightness to: %ld (norm: %d)\n", new_blight, blight); + + backlight_destroy(backlight); +} + +int +main(int argc, char **argv) +{ + int blight, connector_id; + const char *path; + struct udev *udev; + struct udev_enumerate *e; + struct udev_list_entry *entry; + struct udev_device *drm_device; + + if (argc < 3) { + printf("Please add connector_id and brightness values from 0-255\n"); + return 1; + } + + connector_id = atoi(argv[1]); + blight = atoi(argv[2]); + + udev = udev_new(); + if (udev == NULL) { + printf("failed to initialize udev context\n"); + return 1; + } + + e = udev_enumerate_new(udev); + udev_enumerate_add_match_subsystem(e, "drm"); + udev_enumerate_add_match_sysname(e, "card[0-9]*"); + + udev_enumerate_scan_devices(e); + drm_device = NULL; + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { + path = udev_list_entry_get_name(entry); + drm_device = udev_device_new_from_syspath(udev, path); + break; + } + + if (drm_device == NULL) { + printf("no drm device found\n"); + return 1; + } + + set_backlight(drm_device, connector_id, blight); + + udev_device_unref(drm_device); + return 0; +} diff --git a/tests/subsurface-test.c b/tests/subsurface-test.c new file mode 100644 index 00000000..98e00fea --- /dev/null +++ b/tests/subsurface-test.c @@ -0,0 +1,549 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "weston-test-client-helper.h" +#include "subsurface-client-protocol.h" +#include + +#define NUM_SUBSURFACES 3 + +struct compound_surface { + struct wl_subcompositor *subco; + struct wl_surface *parent; + struct wl_surface *child[NUM_SUBSURFACES]; + struct wl_subsurface *sub[NUM_SUBSURFACES]; +}; + +static struct wl_subcompositor * +get_subcompositor(struct client *client) +{ + struct global *g; + struct global *global_sub = NULL; + struct wl_subcompositor *sub; + + wl_list_for_each(g, &client->global_list, link) { + if (strcmp(g->interface, "wl_subcompositor")) + continue; + + if (global_sub) + assert(0 && "multiple wl_subcompositor objects"); + + global_sub = g; + } + + assert(global_sub && "no wl_subcompositor found"); + + assert(global_sub->version == 1); + + sub = wl_registry_bind(client->wl_registry, global_sub->name, + &wl_subcompositor_interface, 1); + assert(sub); + + return sub; +} + +static void +populate_compound_surface(struct compound_surface *com, struct client *client) +{ + int i; + + com->subco = get_subcompositor(client); + + com->parent = wl_compositor_create_surface(client->wl_compositor); + + for (i = 0; i < NUM_SUBSURFACES; i++) { + com->child[i] = + wl_compositor_create_surface(client->wl_compositor); + com->sub[i] = + wl_subcompositor_get_subsurface(com->subco, + com->child[i], + com->parent); + } +} + +TEST(test_subsurface_basic_protocol) +{ + struct client *client; + struct compound_surface com1; + struct compound_surface com2; + + client = client_create(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com1, client); + populate_compound_surface(&com2, client); + + client_roundtrip(client); +} + +TEST(test_subsurface_position_protocol) +{ + struct client *client; + struct compound_surface com; + int i; + + client = client_create(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + for (i = 0; i < NUM_SUBSURFACES; i++) + wl_subsurface_set_position(com.sub[i], + (i + 2) * 20, (i + 2) * 10); + + client_roundtrip(client); +} + +TEST(test_subsurface_placement_protocol) +{ + struct client *client; + struct compound_surface com; + + client = client_create(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + wl_subsurface_place_above(com.sub[0], com.child[1]); + wl_subsurface_place_above(com.sub[1], com.parent); + wl_subsurface_place_below(com.sub[2], com.child[0]); + wl_subsurface_place_below(com.sub[1], com.parent); + + client_roundtrip(client); +} + +FAIL_TEST(test_subsurface_paradox) +{ + struct client *client; + struct wl_surface *parent; + struct wl_subcompositor *subco; + + client = client_create(100, 50, 123, 77); + assert(client); + + subco = get_subcompositor(client); + parent = wl_compositor_create_surface(client->wl_compositor); + + /* surface is its own parent */ + wl_subcompositor_get_subsurface(subco, parent, parent); + + client_roundtrip(client); +} + +FAIL_TEST(test_subsurface_identical_link) +{ + struct client *client; + struct compound_surface com; + + client = client_create(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + /* surface is already a subsurface */ + wl_subcompositor_get_subsurface(com.subco, com.child[0], com.parent); + + client_roundtrip(client); +} + +FAIL_TEST(test_subsurface_change_link) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *stranger; + + client = client_create(100, 50, 123, 77); + assert(client); + + stranger = wl_compositor_create_surface(client->wl_compositor); + populate_compound_surface(&com, client); + + /* surface is already a subsurface */ + wl_subcompositor_get_subsurface(com.subco, com.child[0], stranger); + + client_roundtrip(client); +} + +TEST(test_subsurface_nesting) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *stranger; + + client = client_create(100, 50, 123, 77); + assert(client); + + stranger = wl_compositor_create_surface(client->wl_compositor); + populate_compound_surface(&com, client); + + /* parent is a sub-surface */ + wl_subcompositor_get_subsurface(com.subco, stranger, com.child[0]); + + client_roundtrip(client); +} + +TEST(test_subsurface_nesting_parent) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *stranger; + + client = client_create(100, 50, 123, 77); + assert(client); + + stranger = wl_compositor_create_surface(client->wl_compositor); + populate_compound_surface(&com, client); + + /* surface is already a parent */ + wl_subcompositor_get_subsurface(com.subco, com.parent, stranger); + + client_roundtrip(client); +} + +FAIL_TEST(test_subsurface_loop_paradox) +{ + struct client *client; + struct wl_surface *surface[3]; + struct wl_subcompositor *subco; + + client = client_create(100, 50, 123, 77); + assert(client); + + subco = get_subcompositor(client); + surface[0] = wl_compositor_create_surface(client->wl_compositor); + surface[1] = wl_compositor_create_surface(client->wl_compositor); + surface[2] = wl_compositor_create_surface(client->wl_compositor); + + /* create a nesting loop */ + wl_subcompositor_get_subsurface(subco, surface[1], surface[0]); + wl_subcompositor_get_subsurface(subco, surface[2], surface[1]); + wl_subcompositor_get_subsurface(subco, surface[0], surface[2]); + + client_roundtrip(client); +} + +FAIL_TEST(test_subsurface_place_above_stranger) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *stranger; + + client = client_create(100, 50, 123, 77); + assert(client); + + stranger = wl_compositor_create_surface(client->wl_compositor); + populate_compound_surface(&com, client); + + /* bad sibling */ + wl_subsurface_place_above(com.sub[0], stranger); + + client_roundtrip(client); +} + +FAIL_TEST(test_subsurface_place_below_stranger) +{ + struct client *client; + struct compound_surface com; + struct wl_surface *stranger; + + client = client_create(100, 50, 123, 77); + assert(client); + + stranger = wl_compositor_create_surface(client->wl_compositor); + populate_compound_surface(&com, client); + + /* bad sibling */ + wl_subsurface_place_below(com.sub[0], stranger); + + client_roundtrip(client); +} + +FAIL_TEST(test_subsurface_place_above_foreign) +{ + struct client *client; + struct compound_surface com1; + struct compound_surface com2; + + client = client_create(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com1, client); + populate_compound_surface(&com2, client); + + /* bad sibling */ + wl_subsurface_place_above(com1.sub[0], com2.child[0]); + + client_roundtrip(client); +} + +FAIL_TEST(test_subsurface_place_below_foreign) +{ + struct client *client; + struct compound_surface com1; + struct compound_surface com2; + + client = client_create(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com1, client); + populate_compound_surface(&com2, client); + + /* bad sibling */ + wl_subsurface_place_below(com1.sub[0], com2.child[0]); + + client_roundtrip(client); +} + +TEST(test_subsurface_destroy_protocol) +{ + struct client *client; + struct compound_surface com; + + client = client_create(100, 50, 123, 77); + assert(client); + + populate_compound_surface(&com, client); + + /* not needed anymore */ + wl_subcompositor_destroy(com.subco); + + /* detach child from parent */ + wl_subsurface_destroy(com.sub[0]); + + /* destroy: child, parent */ + wl_surface_destroy(com.child[1]); + wl_surface_destroy(com.parent); + + /* destroy: parent, child */ + wl_surface_destroy(com.child[2]); + + /* destroy: sub, child */ + wl_surface_destroy(com.child[0]); + + /* 2x destroy: child, sub */ + wl_subsurface_destroy(com.sub[2]); + wl_subsurface_destroy(com.sub[1]); + + client_roundtrip(client); +} + +static void +create_subsurface_tree(struct client *client, struct wl_surface **surfs, + struct wl_subsurface **subs, int n) +{ + struct wl_subcompositor *subco; + int i; + + subco = get_subcompositor(client); + + for (i = 0; i < n; i++) + surfs[i] = wl_compositor_create_surface(client->wl_compositor); + + /* + * The tree of sub-surfaces: + * 0 + * / \ + * 1 2 - 10 + * / \ |\ + * 3 5 9 6 + * / / \ + * 4 7 8 + * Surface 0 has no wl_subsurface, others do. + */ + + switch (n) { + default: + assert(0); + break; + +#define SUB_LINK(s,p) \ + subs[s] = wl_subcompositor_get_subsurface(subco, surfs[s], surfs[p]) + + case 11: + SUB_LINK(10, 2); + case 10: + SUB_LINK(9, 2); + case 9: + SUB_LINK(8, 6); + case 8: + SUB_LINK(7, 6); + case 7: + SUB_LINK(6, 2); + case 6: + SUB_LINK(5, 1); + case 5: + SUB_LINK(4, 3); + case 4: + SUB_LINK(3, 1); + case 3: + SUB_LINK(2, 0); + case 2: + SUB_LINK(1, 0); + +#undef SUB_LINK + }; +} + +static void +destroy_subsurface_tree(struct wl_surface **surfs, + struct wl_subsurface **subs, int n) +{ + int i; + + for (i = n; i-- > 0; ) { + if (surfs[i]) + wl_surface_destroy(surfs[i]); + + if (subs[i]) + wl_subsurface_destroy(subs[i]); + } +} + +static int +has_dupe(int *cnt, int n) +{ + int i; + + for (i = 0; i < n; i++) + if (cnt[i] == cnt[n]) + return 1; + + return 0; +} + +/* Note: number of permutations to test is: set_size! / (set_size - NSTEPS)! + */ +#define NSTEPS 3 + +struct permu_state { + int set_size; + int cnt[NSTEPS]; +}; + +static void +permu_init(struct permu_state *s, int set_size) +{ + int i; + + s->set_size = set_size; + for (i = 0; i < NSTEPS; i++) + s->cnt[i] = 0; +} + +static int +permu_next(struct permu_state *s) +{ + int k; + + s->cnt[NSTEPS - 1]++; + + while (1) { + if (s->cnt[0] >= s->set_size) { + return -1; + } + + for (k = 1; k < NSTEPS; k++) { + if (s->cnt[k] >= s->set_size) { + s->cnt[k - 1]++; + s->cnt[k] = 0; + break; + } + + if (has_dupe(s->cnt, k)) { + s->cnt[k]++; + break; + } + } + + if (k == NSTEPS) + return 0; + } +} + +static void +destroy_permu_object(struct wl_surface **surfs, + struct wl_subsurface **subs, int i) +{ + int h = (i + 1) / 2; + + if (i & 1) { + fprintf(stderr, " [sub %2d]", h); + wl_subsurface_destroy(subs[h]); + subs[h] = NULL; + } else { + fprintf(stderr, " [surf %2d]", h); + wl_surface_destroy(surfs[h]); + surfs[h] = NULL; + } +} + +TEST(test_subsurface_destroy_permutations) +{ + /* + * Test wl_surface and wl_subsurface destruction orders in a + * complex tree of sub-surfaces. + * + * In the tree of sub-surfaces, go through every possible + * permutation of destroying all wl_surface and wl_subsurface + * objects. Execpt, to limit running time to a reasonable level, + * execute only the first NSTEPS destructions from each + * permutation, and ignore identical cases. + */ + + const int test_size = 11; + struct client *client; + struct wl_surface *surfs[test_size]; + struct wl_subsurface *subs[test_size]; + struct permu_state per; + int counter = 0; + int i; + + client = client_create(100, 50, 123, 77); + assert(client); + + permu_init(&per, test_size * 2 - 1); + while (permu_next(&per) != -1) { + /* for each permutation of NSTEPS out of test_size */ + memset(surfs, 0, sizeof surfs); + memset(subs, 0, sizeof subs); + + create_subsurface_tree(client, surfs, subs, test_size); + + fprintf(stderr, "permu"); + + for (i = 0; i < NSTEPS; i++) + fprintf(stderr, " %2d", per.cnt[i]); + + for (i = 0; i < NSTEPS; i++) + destroy_permu_object(surfs, subs, per.cnt[i]); + + fprintf(stderr, "\n"); + client_roundtrip(client); + + destroy_subsurface_tree(surfs, subs, test_size); + counter++; + } + + client_roundtrip(client); + fprintf(stderr, "tried %d destroy permutations\n", counter); +} diff --git a/tests/surface-global-test.c b/tests/surface-global-test.c new file mode 100644 index 00000000..04b64d6a --- /dev/null +++ b/tests/surface-global-test.c @@ -0,0 +1,80 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "../src/compositor.h" + +static void +surface_to_from_global(void *data) +{ + struct weston_compositor *compositor = data; + struct weston_surface *surface; + float x, y; + wl_fixed_t fx, fy; + int32_t ix, iy; + + surface = weston_surface_create(compositor); + assert(surface); + weston_surface_configure(surface, 5, 10, 50, 50); + weston_surface_update_transform(surface); + + weston_surface_to_global_float(surface, 33, 22, &x, &y); + assert(x == 38 && y == 32); + + weston_surface_to_global_float(surface, -8, -2, &x, &y); + assert(x == -3 && y == 8); + + weston_surface_to_global_fixed(surface, wl_fixed_from_int(12), + wl_fixed_from_int(5), &fx, &fy); + assert(fx == wl_fixed_from_int(17) && fy == wl_fixed_from_int(15)); + + weston_surface_from_global_float(surface, 38, 32, &x, &y); + assert(x == 33 && y == 22); + + weston_surface_from_global_float(surface, 42, 5, &x, &y); + assert(x == 37 && y == -5); + + weston_surface_from_global_fixed(surface, wl_fixed_from_int(21), + wl_fixed_from_int(100), &fx, &fy); + assert(fx == wl_fixed_from_int(16) && fy == wl_fixed_from_int(90)); + + weston_surface_from_global(surface, 0, 0, &ix, &iy); + assert(ix == -5 && iy == -10); + + weston_surface_from_global(surface, 5, 10, &ix, &iy); + assert(ix == 0 && iy == 0); + + wl_display_terminate(compositor->wl_display); +} + +WL_EXPORT int +module_init(struct weston_compositor *compositor, int *argc, char *argv[]) +{ + struct wl_event_loop *loop; + + loop = wl_display_get_event_loop(compositor->wl_display); + + wl_event_loop_add_idle(loop, surface_to_from_global, compositor); + + return 0; +} diff --git a/tests/surface-test.c b/tests/surface-test.c new file mode 100644 index 00000000..e8af2ed7 --- /dev/null +++ b/tests/surface-test.c @@ -0,0 +1,63 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include "../src/compositor.h" + +static void +surface_transform(void *data) +{ + struct weston_compositor *compositor = data; + struct weston_surface *surface; + float x, y; + + surface = weston_surface_create(compositor); + assert(surface); + weston_surface_configure(surface, 100, 100, 200, 200); + weston_surface_update_transform(surface); + weston_surface_to_global_float(surface, 20, 20, &x, &y); + + fprintf(stderr, "20,20 maps to %f, %f\n", x, y); + assert(x == 120 && y == 120); + + weston_surface_set_position(surface, 150, 300); + weston_surface_update_transform(surface); + weston_surface_to_global_float(surface, 50, 40, &x, &y); + assert(x == 200 && y == 340); + + wl_display_terminate(compositor->wl_display); +} + +WL_EXPORT int +module_init(struct weston_compositor *compositor, int *argc, char *argv[]) +{ + struct wl_event_loop *loop; + + loop = wl_display_get_event_loop(compositor->wl_display); + + wl_event_loop_add_idle(loop, surface_transform, compositor); + + return 0; +} diff --git a/tests/text-test.c b/tests/text-test.c new file mode 100644 index 00000000..48f2b5a6 --- /dev/null +++ b/tests/text-test.c @@ -0,0 +1,214 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include "weston-test-client-helper.h" +#include "../clients/text-client-protocol.h" + +struct text_input_state { + int activated; + int deactivated; +}; + +static void +text_input_commit_string(void *data, + struct wl_text_input *text_input, + uint32_t serial, + const char *text) +{ +} + +static void +text_input_preedit_string(void *data, + struct wl_text_input *text_input, + uint32_t serial, + const char *text, + const char *commit) +{ +} + +static void +text_input_delete_surrounding_text(void *data, + struct wl_text_input *text_input, + int32_t index, + uint32_t length) +{ +} + +static void +text_input_cursor_position(void *data, + struct wl_text_input *text_input, + int32_t index, + int32_t anchor) +{ +} + +static void +text_input_preedit_styling(void *data, + struct wl_text_input *text_input, + uint32_t index, + uint32_t length, + uint32_t style) +{ +} + +static void +text_input_preedit_cursor(void *data, + struct wl_text_input *text_input, + int32_t index) +{ +} + +static void +text_input_modifiers_map(void *data, + struct wl_text_input *text_input, + struct wl_array *map) +{ +} + +static void +text_input_keysym(void *data, + struct wl_text_input *text_input, + uint32_t serial, + uint32_t time, + uint32_t sym, + uint32_t state, + uint32_t modifiers) +{ +} + +static void +text_input_enter(void *data, + struct wl_text_input *text_input, + struct wl_surface *surface) + +{ + struct text_input_state *state = data; + + fprintf(stderr, "%s\n", __FUNCTION__); + + state->activated += 1; +} + +static void +text_input_leave(void *data, + struct wl_text_input *text_input) +{ + struct text_input_state *state = data; + + state->deactivated += 1; +} + +static void +text_input_input_panel_state(void *data, + struct wl_text_input *text_input, + uint32_t state) +{ +} + +static void +text_input_language(void *data, + struct wl_text_input *text_input, + uint32_t serial, + const char *language) +{ +} + +static void +text_input_text_direction(void *data, + struct wl_text_input *text_input, + uint32_t serial, + uint32_t direction) +{ +} + +static const struct wl_text_input_listener text_input_listener = { + text_input_enter, + text_input_leave, + text_input_modifiers_map, + text_input_input_panel_state, + text_input_preedit_string, + text_input_preedit_styling, + text_input_preedit_cursor, + text_input_commit_string, + text_input_cursor_position, + text_input_delete_surrounding_text, + text_input_keysym, + text_input_language, + text_input_text_direction +}; + +TEST(text_test) +{ + struct client *client; + struct global *global; + struct wl_text_input_manager *factory; + struct wl_text_input *text_input; + struct text_input_state state; + + client = client_create(100, 100, 100, 100); + assert(client); + + factory = NULL; + wl_list_for_each(global, &client->global_list, link) { + if (strcmp(global->interface, "wl_text_input_manager") == 0) + factory = wl_registry_bind(client->wl_registry, + global->name, + &wl_text_input_manager_interface, 1); + } + + assert(factory); + + memset(&state, 0, sizeof state); + text_input = wl_text_input_manager_create_text_input(factory); + wl_text_input_add_listener(text_input, &text_input_listener, &state); + + /* Make sure our test surface has keyboard focus. */ + wl_test_activate_surface(client->test->wl_test, + client->surface->wl_surface); + client_roundtrip(client); + assert(client->input->keyboard->focus == client->surface); + + /* Activate test model and make sure we get enter event. */ + wl_text_input_activate(text_input, client->input->wl_seat, + client->surface->wl_surface); + client_roundtrip(client); + assert(state.activated == 1 && state.deactivated == 0); + + /* Deactivate test model and make sure we get leave event. */ + wl_text_input_deactivate(text_input, client->input->wl_seat); + client_roundtrip(client); + assert(state.activated == 1 && state.deactivated == 1); + + /* Activate test model again. */ + wl_text_input_activate(text_input, client->input->wl_seat, + client->surface->wl_surface); + client_roundtrip(client); + assert(state.activated == 2 && state.deactivated == 1); + + /* Take keyboard focus away and verify we get leave event. */ + wl_test_activate_surface(client->test->wl_test, NULL); + client_roundtrip(client); + assert(state.activated == 2 && state.deactivated == 2); +} diff --git a/tests/vertex-clip-test.c b/tests/vertex-clip-test.c new file mode 100644 index 00000000..5b2e08c5 --- /dev/null +++ b/tests/vertex-clip-test.c @@ -0,0 +1,219 @@ +/* + * Copyright © 2013 Sam Spilsbury + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include + +#include "weston-test-runner.h" + +#include "../src/vertex-clipping.h" + +#define BOUNDING_BOX_TOP_Y 100.0f +#define BOUNDING_BOX_LEFT_X 50.0f +#define BOUNDING_BOX_RIGHT_X 100.0f +#define BOUNDING_BOX_BOTTOM_Y 50.0f + +#define INSIDE_X1 (BOUNDING_BOX_LEFT_X + 1.0f) +#define INSIDE_X2 (BOUNDING_BOX_RIGHT_X - 1.0f) +#define INSIDE_Y1 (BOUNDING_BOX_BOTTOM_Y + 1.0f) +#define INSIDE_Y2 (BOUNDING_BOX_TOP_Y - 1.0f) + +#define OUTSIDE_X1 (BOUNDING_BOX_LEFT_X - 1.0f) +#define OUTSIDE_X2 (BOUNDING_BOX_RIGHT_X + 1.0f) +#define OUTSIDE_Y1 (BOUNDING_BOX_BOTTOM_Y - 1.0f) +#define OUTSIDE_Y2 (BOUNDING_BOX_TOP_Y + 1.0f) + +static void +populate_clip_context (struct clip_context *ctx) +{ + ctx->clip.x1 = BOUNDING_BOX_LEFT_X; + ctx->clip.y1 = BOUNDING_BOX_BOTTOM_Y; + ctx->clip.x2 = BOUNDING_BOX_RIGHT_X; + ctx->clip.y2 = BOUNDING_BOX_TOP_Y; +} + +static int +clip_polygon (struct clip_context *ctx, + struct polygon8 *polygon, + GLfloat *vertices_x, + GLfloat *vertices_y) +{ + populate_clip_context(ctx); + return clip_transformed(ctx, polygon, vertices_x, vertices_y); +} + +struct vertex_clip_test_data +{ + struct polygon8 surface; + struct polygon8 expected; +}; + +const struct vertex_clip_test_data test_data[] = +{ + /* All inside */ + { + { + { INSIDE_X1, INSIDE_X2, INSIDE_X2, INSIDE_X1 }, + { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, + 4 + }, + { + { INSIDE_X1, INSIDE_X2, INSIDE_X2, INSIDE_X1 }, + { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, + 4 + } + }, + /* Top outside */ + { + { + { INSIDE_X1, INSIDE_X2, INSIDE_X2, INSIDE_X1 }, + { INSIDE_Y1, INSIDE_Y1, OUTSIDE_Y2, OUTSIDE_Y2 }, + 4 + }, + { + { INSIDE_X1, INSIDE_X1, INSIDE_X2, INSIDE_X2 }, + { BOUNDING_BOX_TOP_Y, INSIDE_Y1, INSIDE_Y1, BOUNDING_BOX_TOP_Y }, + 4 + } + }, + /* Bottom outside */ + { + { + { INSIDE_X1, INSIDE_X2, INSIDE_X2, INSIDE_X1 }, + { OUTSIDE_Y1, OUTSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, + 4 + }, + { + { INSIDE_X1, INSIDE_X2, INSIDE_X2, INSIDE_X1 }, + { BOUNDING_BOX_BOTTOM_Y, BOUNDING_BOX_BOTTOM_Y, INSIDE_Y2, INSIDE_Y2 }, + 4 + } + }, + /* Left outside */ + { + { + { OUTSIDE_X1, INSIDE_X2, INSIDE_X2, OUTSIDE_X1 }, + { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, + 4 + }, + { + { BOUNDING_BOX_LEFT_X, INSIDE_X2, INSIDE_X2, BOUNDING_BOX_LEFT_X }, + { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, + 4 + } + }, + /* Right outside */ + { + { + { INSIDE_X1, OUTSIDE_X2, OUTSIDE_X2, INSIDE_X1 }, + { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, + 4 + }, + { + { INSIDE_X1, BOUNDING_BOX_RIGHT_X, BOUNDING_BOX_RIGHT_X, INSIDE_X1 }, + { INSIDE_Y1, INSIDE_Y1, INSIDE_Y2, INSIDE_Y2 }, + 4 + } + }, + /* Diamond extending from bounding box edges, clip to bounding box */ + { + { + { BOUNDING_BOX_LEFT_X - 25, BOUNDING_BOX_LEFT_X + 25, BOUNDING_BOX_RIGHT_X + 25, BOUNDING_BOX_RIGHT_X - 25 }, + { BOUNDING_BOX_BOTTOM_Y + 25, BOUNDING_BOX_TOP_Y + 25, BOUNDING_BOX_TOP_Y - 25, BOUNDING_BOX_BOTTOM_Y - 25 }, + 4 + }, + { + { BOUNDING_BOX_LEFT_X, BOUNDING_BOX_LEFT_X, BOUNDING_BOX_RIGHT_X, BOUNDING_BOX_RIGHT_X }, + { BOUNDING_BOX_BOTTOM_Y, BOUNDING_BOX_TOP_Y, BOUNDING_BOX_TOP_Y, BOUNDING_BOX_BOTTOM_Y }, + 4 + } + }, + /* Diamond inside of bounding box edges, clip t bounding box, 8 resulting vertices */ + { + { + { BOUNDING_BOX_LEFT_X - 12.5, BOUNDING_BOX_LEFT_X + 25, BOUNDING_BOX_RIGHT_X + 12.5, BOUNDING_BOX_RIGHT_X - 25 }, + { BOUNDING_BOX_BOTTOM_Y + 25, BOUNDING_BOX_TOP_Y + 12.5, BOUNDING_BOX_TOP_Y - 25, BOUNDING_BOX_BOTTOM_Y - 12.5 }, + 4 + }, + { + { BOUNDING_BOX_LEFT_X + 12.5, BOUNDING_BOX_LEFT_X, BOUNDING_BOX_LEFT_X, BOUNDING_BOX_LEFT_X + 12.5, + BOUNDING_BOX_RIGHT_X - 12.5, BOUNDING_BOX_RIGHT_X, BOUNDING_BOX_RIGHT_X, BOUNDING_BOX_RIGHT_X - 12.5 }, + { BOUNDING_BOX_BOTTOM_Y, BOUNDING_BOX_BOTTOM_Y + 12.5, BOUNDING_BOX_TOP_Y - 12.5, BOUNDING_BOX_TOP_Y, + BOUNDING_BOX_TOP_Y, BOUNDING_BOX_TOP_Y - 12.5, BOUNDING_BOX_BOTTOM_Y + 12.5, BOUNDING_BOX_BOTTOM_Y }, + 8 + } + } +}; + +/* clip_polygon modifies the source operand and the test data must + * be const, so we need to deep copy it */ +static void +deep_copy_polygon8(const struct polygon8 *src, struct polygon8 *dst) +{ + dst->n = src->n; + memcpy((void *) dst->x, src->x, sizeof (src->x)); + memcpy((void *) dst->y, src->y, sizeof (src->y)); +} + +TEST_P(clip_polygon_n_vertices_emitted, test_data) +{ + struct vertex_clip_test_data *tdata = data; + struct clip_context ctx; + struct polygon8 polygon; + GLfloat vertices_x[8]; + GLfloat vertices_y[8]; + deep_copy_polygon8(&tdata->surface, &polygon); + int emitted = clip_polygon(&ctx, &polygon, vertices_x, vertices_y); + + assert(emitted == tdata->expected.n); +} + +TEST_P(clip_polygon_expected_vertices, test_data) +{ + struct vertex_clip_test_data *tdata = data; + struct clip_context ctx; + struct polygon8 polygon; + GLfloat vertices_x[8]; + GLfloat vertices_y[8]; + deep_copy_polygon8(&tdata->surface, &polygon); + int emitted = clip_polygon(&ctx, &polygon, vertices_x, vertices_y); + int i = 0; + + for (; i < emitted; ++i) + { + assert(vertices_x[i] == tdata->expected.x[i]); + assert(vertices_y[i] == tdata->expected.y[i]); + } +} + +TEST(float_difference_different) +{ + assert(float_difference(1.0f, 0.0f) == 1.0f); +} + +TEST(float_difference_same) +{ + assert(float_difference(1.0f, 1.0f) == 0.0f); +} + diff --git a/tests/weston-test-client-helper.c b/tests/weston-test-client-helper.c new file mode 100644 index 00000000..b19be400 --- /dev/null +++ b/tests/weston-test-client-helper.c @@ -0,0 +1,539 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "../shared/os-compatibility.h" +#include "weston-test-client-helper.h" + +static inline void * +xzalloc(size_t size) +{ + void *p; + + p = calloc(1, size); + assert(p); + + return p; +} + +int +surface_contains(struct surface *surface, int x, int y) +{ + /* test whether a global x,y point is contained in the surface */ + int sx = surface->x; + int sy = surface->y; + int sw = surface->width; + int sh = surface->height; + return x >= sx && y >= sy && x < sx + sw && y < sy + sh; +} + +static void +frame_callback_handler(void *data, struct wl_callback *callback, uint32_t time) +{ + int *done = data; + + *done = 1; + + wl_callback_destroy(callback); +} + +static const struct wl_callback_listener frame_listener = { + frame_callback_handler +}; + +struct wl_callback * +frame_callback_set(struct wl_surface *surface, int *done) +{ + struct wl_callback *callback; + + *done = 0; + callback = wl_surface_frame(surface); + wl_callback_add_listener(callback, &frame_listener, done); + + return callback; +} + +void +frame_callback_wait(struct client *client, int *done) +{ + while (!*done) { + assert(wl_display_dispatch(client->wl_display) >= 0); + } +} + +void +move_client(struct client *client, int x, int y) +{ + struct surface *surface = client->surface; + int done; + + client->surface->x = x; + client->surface->y = y; + wl_test_move_surface(client->test->wl_test, surface->wl_surface, + surface->x, surface->y); + /* The attach here is necessary because commit() will call congfigure + * only on surfaces newly attached, and the one that sets the surface + * position is the configure. */ + wl_surface_attach(surface->wl_surface, surface->wl_buffer, 0, 0); + wl_surface_damage(surface->wl_surface, 0, 0, surface->width, + surface->height); + + frame_callback_set(surface->wl_surface, &done); + + wl_surface_commit(surface->wl_surface); + + frame_callback_wait(client, &done); +} + +static void +pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *wl_surface, + wl_fixed_t x, wl_fixed_t y) +{ + struct pointer *pointer = data; + + pointer->focus = wl_surface_get_user_data(wl_surface); + pointer->x = wl_fixed_to_int(x); + pointer->y = wl_fixed_to_int(y); + + fprintf(stderr, "test-client: got pointer enter %d %d, surface %p\n", + pointer->x, pointer->y, pointer->focus); +} + +static void +pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *wl_surface) +{ + struct pointer *pointer = data; + + pointer->focus = NULL; + + fprintf(stderr, "test-client: got pointer leave, surface %p\n", + wl_surface_get_user_data(wl_surface)); +} + +static void +pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t x, wl_fixed_t y) +{ + struct pointer *pointer = data; + + pointer->x = wl_fixed_to_int(x); + pointer->y = wl_fixed_to_int(y); + + fprintf(stderr, "test-client: got pointer motion %d %d\n", + pointer->x, pointer->y); +} + +static void +pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, uint32_t button, + uint32_t state) +{ + struct pointer *pointer = data; + + pointer->button = button; + pointer->state = state; + + fprintf(stderr, "test-client: got pointer button %u %u\n", + button, state); +} + +static void +pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ + fprintf(stderr, "test-client: got pointer axis %u %f\n", + axis, wl_fixed_to_double(value)); +} + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, +}; + +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int fd, uint32_t size) +{ + close(fd); + + fprintf(stderr, "test-client: got keyboard keymap\n"); +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *wl_surface, + struct wl_array *keys) +{ + struct keyboard *keyboard = data; + + keyboard->focus = wl_surface_get_user_data(wl_surface); + + fprintf(stderr, "test-client: got keyboard enter, surface %p\n", + keyboard->focus); +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *wl_surface) +{ + struct keyboard *keyboard = data; + + keyboard->focus = NULL; + + fprintf(stderr, "test-client: got keyboard leave, surface %p\n", + wl_surface_get_user_data(wl_surface)); +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ + struct keyboard *keyboard = data; + + keyboard->key = key; + keyboard->state = state; + + fprintf(stderr, "test-client: got keyboard key %u %u\n", key, state); +} + +static void +keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ + struct keyboard *keyboard = data; + + keyboard->mods_depressed = mods_depressed; + keyboard->mods_latched = mods_latched; + keyboard->mods_locked = mods_locked; + keyboard->group = group; + + fprintf(stderr, "test-client: got keyboard modifiers %u %u %u %u\n", + mods_depressed, mods_latched, mods_locked, group); +} + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, +}; + +static void +surface_enter(void *data, + struct wl_surface *wl_surface, struct wl_output *output) +{ + struct surface *surface = data; + + surface->output = wl_output_get_user_data(output); + + fprintf(stderr, "test-client: got surface enter output %p\n", + surface->output); +} + +static void +surface_leave(void *data, + struct wl_surface *wl_surface, struct wl_output *output) +{ + struct surface *surface = data; + + surface->output = NULL; + + fprintf(stderr, "test-client: got surface leave output %p\n", + wl_output_get_user_data(output)); +} + +static const struct wl_surface_listener surface_listener = { + surface_enter, + surface_leave +}; + +struct wl_buffer * +create_shm_buffer(struct client *client, int width, int height, void **pixels) +{ + struct wl_shm *shm = client->wl_shm; + int stride = width * 4; + int size = stride * height; + struct wl_shm_pool *pool; + struct wl_buffer *buffer; + int fd; + void *data; + + fd = os_create_anonymous_file(size); + assert(fd >= 0); + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + close(fd); + assert(data != MAP_FAILED); + } + + pool = wl_shm_create_pool(shm, fd, size); + buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, + WL_SHM_FORMAT_ARGB8888); + wl_shm_pool_destroy(pool); + + close(fd); + + if (pixels) + *pixels = data; + + return buffer; +} + +static void +shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct client *client = data; + + if (format == WL_SHM_FORMAT_ARGB8888) + client->has_argb = 1; +} + +struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +test_handle_pointer_position(void *data, struct wl_test *wl_test, + wl_fixed_t x, wl_fixed_t y) +{ + struct test *test = data; + test->pointer_x = wl_fixed_to_int(x); + test->pointer_y = wl_fixed_to_int(y); + + fprintf(stderr, "test-client: got global pointer %d %d\n", + test->pointer_x, test->pointer_y); +} + +static const struct wl_test_listener test_listener = { + test_handle_pointer_position +}; + +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + struct input *input = data; + struct pointer *pointer; + struct keyboard *keyboard; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !input->pointer) { + pointer = xzalloc(sizeof *pointer); + pointer->wl_pointer = wl_seat_get_pointer(seat); + wl_pointer_set_user_data(pointer->wl_pointer, pointer); + wl_pointer_add_listener(pointer->wl_pointer, &pointer_listener, + pointer); + input->pointer = pointer; + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->pointer) { + wl_pointer_destroy(input->pointer->wl_pointer); + free(input->pointer); + input->pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard) { + keyboard = xzalloc(sizeof *keyboard); + keyboard->wl_keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_set_user_data(keyboard->wl_keyboard, keyboard); + wl_keyboard_add_listener(keyboard->wl_keyboard, &keyboard_listener, + keyboard); + input->keyboard = keyboard; + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard) { + wl_keyboard_destroy(input->keyboard->wl_keyboard); + free(input->keyboard); + input->keyboard = NULL; + } +} + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, +}; + +static void +output_handle_geometry(void *data, + struct wl_output *wl_output, + int x, int y, + int physical_width, + int physical_height, + int subpixel, + const char *make, + const char *model, + int32_t transform) +{ + struct output *output = data; + + output->x = x; + output->y = y; +} + +static void +output_handle_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int width, + int height, + int refresh) +{ + struct output *output = data; + + if (flags & WL_OUTPUT_MODE_CURRENT) { + output->width = width; + output->height = height; + } +} + +static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode +}; + +static void +handle_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, uint32_t version) +{ + struct client *client = data; + struct input *input; + struct output *output; + struct test *test; + struct global *global; + + global = xzalloc(sizeof *global); + global->name = id; + global->interface = strdup(interface); + assert(interface); + global->version = version; + wl_list_insert(client->global_list.prev, &global->link); + + if (strcmp(interface, "wl_compositor") == 0) { + client->wl_compositor = + wl_registry_bind(registry, id, + &wl_compositor_interface, 1); + } else if (strcmp(interface, "wl_seat") == 0) { + input = xzalloc(sizeof *input); + input->wl_seat = + wl_registry_bind(registry, id, + &wl_seat_interface, 1); + wl_seat_add_listener(input->wl_seat, &seat_listener, input); + client->input = input; + } else if (strcmp(interface, "wl_shm") == 0) { + client->wl_shm = + wl_registry_bind(registry, id, + &wl_shm_interface, 1); + wl_shm_add_listener(client->wl_shm, &shm_listener, client); + } else if (strcmp(interface, "wl_output") == 0) { + output = xzalloc(sizeof *output); + output->wl_output = + wl_registry_bind(registry, id, + &wl_output_interface, 1); + wl_output_add_listener(output->wl_output, + &output_listener, output); + client->output = output; + } else if (strcmp(interface, "wl_test") == 0) { + test = xzalloc(sizeof *test); + test->wl_test = + wl_registry_bind(registry, id, + &wl_test_interface, 1); + wl_test_add_listener(test->wl_test, &test_listener, test); + client->test = test; + } +} + +static const struct wl_registry_listener registry_listener = { + handle_global +}; + +static void +log_handler(const char *fmt, va_list args) +{ + fprintf(stderr, "libwayland: "); + vfprintf(stderr, fmt, args); +} + +struct client * +client_create(int x, int y, int width, int height) +{ + struct client *client; + struct surface *surface; + + wl_log_set_handler_client(log_handler); + + /* connect to display */ + client = xzalloc(sizeof *client); + client->wl_display = wl_display_connect(NULL); + assert(client->wl_display); + wl_list_init(&client->global_list); + + /* setup registry so we can bind to interfaces */ + client->wl_registry = wl_display_get_registry(client->wl_display); + wl_registry_add_listener(client->wl_registry, ®istry_listener, client); + + /* trigger global listener */ + wl_display_dispatch(client->wl_display); + wl_display_roundtrip(client->wl_display); + + /* must have WL_SHM_FORMAT_ARGB32 */ + assert(client->has_argb); + + /* must have wl_test interface */ + assert(client->test); + + /* must have an output */ + assert(client->output); + + /* initialize the client surface */ + surface = xzalloc(sizeof *surface); + surface->wl_surface = + wl_compositor_create_surface(client->wl_compositor); + assert(surface->wl_surface); + + wl_surface_add_listener(surface->wl_surface, &surface_listener, + surface); + + client->surface = surface; + wl_surface_set_user_data(surface->wl_surface, surface); + + surface->width = width; + surface->height = height; + surface->wl_buffer = create_shm_buffer(client, width, height, + &surface->data); + + memset(surface->data, 64, width * height * 4); + + move_client(client, x, y); + + return client; +} diff --git a/tests/weston-test-client-helper.h b/tests/weston-test-client-helper.h new file mode 100644 index 00000000..a5edca90 --- /dev/null +++ b/tests/weston-test-client-helper.h @@ -0,0 +1,123 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _WESTON_TEST_CLIENT_HELPER_H_ +#define _WESTON_TEST_CLIENT_HELPER_H_ + +#include +#include "weston-test-runner.h" +#include "wayland-test-client-protocol.h" + +struct client { + struct wl_display *wl_display; + struct wl_registry *wl_registry; + struct wl_compositor *wl_compositor; + struct wl_shm *wl_shm; + struct test *test; + struct input *input; + struct output *output; + struct surface *surface; + int has_argb; + struct wl_list global_list; +}; + +struct global { + uint32_t name; + char *interface; + uint32_t version; + struct wl_list link; +}; + +struct test { + struct wl_test *wl_test; + int pointer_x; + int pointer_y; +}; + +struct input { + struct wl_seat *wl_seat; + struct pointer *pointer; + struct keyboard *keyboard; +}; + +struct pointer { + struct wl_pointer *wl_pointer; + struct surface *focus; + int x; + int y; + uint32_t button; + uint32_t state; +}; + +struct keyboard { + struct wl_keyboard *wl_keyboard; + struct surface *focus; + uint32_t key; + uint32_t state; + uint32_t mods_depressed; + uint32_t mods_latched; + uint32_t mods_locked; + uint32_t group; +}; + +struct output { + struct wl_output *wl_output; + int x; + int y; + int width; + int height; +}; + +struct surface { + struct wl_surface *wl_surface; + struct wl_buffer *wl_buffer; + struct output *output; + int x; + int y; + int width; + int height; + void *data; +}; + +struct client * +client_create(int x, int y, int width, int height); + +struct wl_buffer * +create_shm_buffer(struct client *client, int width, int height, void **pixels); + +int +surface_contains(struct surface *surface, int x, int y); + +void +move_client(struct client *client, int x, int y); + +#define client_roundtrip(c) do { \ + assert(wl_display_roundtrip((c)->wl_display) >= 0); \ +} while (0) + +struct wl_callback * +frame_callback_set(struct wl_surface *surface, int *done); + +void +frame_callback_wait(struct client *client, int *done); + +#endif diff --git a/tests/weston-test-runner.c b/tests/weston-test-runner.c new file mode 100644 index 00000000..4274b393 --- /dev/null +++ b/tests/weston-test-runner.c @@ -0,0 +1,167 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "weston-test-runner.h" + +extern const struct weston_test __start_test_section, __stop_test_section; + +static const struct weston_test * +find_test(const char *name) +{ + const struct weston_test *t; + + for (t = &__start_test_section; t < &__stop_test_section; t++) + if (strcmp(t->name, name) == 0) + return t; + + return NULL; +} + +static void +run_test(const struct weston_test *t, void *data) +{ + t->run(data); + exit(EXIT_SUCCESS); +} + +static void +list_tests(void) +{ + const struct weston_test *t; + + fprintf(stderr, "Available test names:\n"); + for (t = &__start_test_section; t < &__stop_test_section; t++) + fprintf(stderr, " %s\n", t->name); +} + +static int +exec_and_report_test(const struct weston_test *t, void *test_data, int iteration) +{ + int success = 0; + int hardfail = 0; + siginfo_t info; + + pid_t pid = fork(); + assert(pid >= 0); + + if (pid == 0) + run_test(t, test_data); /* never returns */ + + if (waitid(P_ALL, 0, &info, WEXITED)) { + fprintf(stderr, "waitid failed: %m\n"); + abort(); + } + + if (test_data) + fprintf(stderr, "test \"%s/%i\":\t", t->name, iteration); + else + fprintf(stderr, "test \"%s\":\t", t->name); + + switch (info.si_code) { + case CLD_EXITED: + fprintf(stderr, "exit status %d", info.si_status); + if (info.si_status == EXIT_SUCCESS) + success = 1; + break; + case CLD_KILLED: + case CLD_DUMPED: + fprintf(stderr, "signal %d", info.si_status); + if (info.si_status != SIGABRT) + hardfail = 1; + break; + } + + if (t->must_fail) + success = !success; + + if (success && !hardfail) { + fprintf(stderr, ", pass.\n"); + return 1; + } else { + fprintf(stderr, ", fail.\n"); + return 0; + } +} + +/* Returns number of tests and number of pass / fail in param args */ +static int +iterate_test(const struct weston_test *t, int *passed) +{ + int i; + void *current_test_data = (void *) t->table_data; + for (i = 0; i < t->n_elements; ++i, current_test_data += t->element_size) + { + if (exec_and_report_test(t, current_test_data, i)) + ++(*passed); + } + + return t->n_elements; +} + +int main(int argc, char *argv[]) +{ + const struct weston_test *t; + int total = 0; + int pass = 0; + + if (argc == 2) { + const char *testname = argv[1]; + if (strcmp(testname, "--help") == 0 || + strcmp(testname, "-h") == 0) { + fprintf(stderr, "Usage: %s [test-name]\n", program_invocation_short_name); + list_tests(); + exit(EXIT_SUCCESS); + } + + t = find_test(argv[1]); + if (t == NULL) { + fprintf(stderr, "unknown test: \"%s\"\n", argv[1]); + list_tests(); + exit(EXIT_FAILURE); + } + + int number_passed_in_test = 0; + total += iterate_test(t, &number_passed_in_test); + pass += number_passed_in_test; + } else { + for (t = &__start_test_section; t < &__stop_test_section; t++) { + int number_passed_in_test = 0; + total += iterate_test(t, &number_passed_in_test); + pass += number_passed_in_test; + } + } + + fprintf(stderr, "%d tests, %d pass, %d fail\n", + total, pass, total - pass); + + return pass == total ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/weston-test-runner.h b/tests/weston-test-runner.h new file mode 100644 index 00000000..457cf31c --- /dev/null +++ b/tests/weston-test-runner.h @@ -0,0 +1,76 @@ +/* + * Copyright © 2012 Intel Corporation + * Copyright © 2013 Sam Spilsbury + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _WESTON_TEST_RUNNER_H_ +#define _WESTON_TEST_RUNNER_H_ + +#include + +#ifdef NDEBUG +#error "Tests must not be built with NDEBUG defined, they rely on assert()." +#endif + +struct weston_test { + const char *name; + void (*run)(void *); + const void *table_data; + size_t element_size; + int n_elements; + int must_fail; +} __attribute__ ((aligned (32))); + +#define TEST_BEGIN(name, arg) \ + static void name(arg) + +#define TEST_COMMON(func, name, ret, data, size, n_elem) \ + static void func(void *); \ + \ + const struct weston_test test##name \ + __attribute__ ((section ("test_section"))) = \ + { \ + #name, func, data, size, n_elem, ret \ + }; + +#define NO_ARG_TEST(name, ret) \ + TEST_COMMON(wrap##name, name, ret, NULL, 0, 1) \ + static void name(void); \ + static void wrap##name(void *data) \ + { \ + (void) data; \ + name(); \ + } \ + \ + TEST_BEGIN(name, void) + +#define ARG_TEST(name, ret, test_data) \ + TEST_COMMON(name, name, ret, test_data, \ + sizeof(test_data[0]), \ + sizeof(test_data) / sizeof (test_data[0])) \ + TEST_BEGIN(name, void *data) \ + +#define TEST(name) NO_ARG_TEST(name, 0) +#define FAIL_TEST(name) NO_ARG_TEST(name, 1) +#define TEST_P(name, data) ARG_TEST(name, 0, data) +#define FAIL_TEST_P(name, data) ARG_TEST(name, 1, data) + +#endif diff --git a/tests/weston-test.c b/tests/weston-test.c new file mode 100644 index 00000000..bc5b6e9d --- /dev/null +++ b/tests/weston-test.c @@ -0,0 +1,250 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include "../src/compositor.h" +#include "wayland-test-server-protocol.h" + +struct weston_test { + struct weston_compositor *compositor; + struct weston_layer layer; + struct weston_process process; +}; + +struct weston_test_surface { + struct weston_surface *surface; + int32_t x, y; + struct weston_test *test; +}; + +static void +test_client_sigchld(struct weston_process *process, int status) +{ + struct weston_test *test = + container_of(process, struct weston_test, process); + + assert(status == 0); + + wl_display_terminate(test->compositor->wl_display); +} + +static struct weston_seat * +get_seat(struct weston_test *test) +{ + struct wl_list *seat_list; + struct weston_seat *seat; + + seat_list = &test->compositor->seat_list; + assert(wl_list_length(seat_list) == 1); + seat = container_of(seat_list->next, struct weston_seat, link); + + return seat; +} + +static void +notify_pointer_position(struct weston_test *test, struct wl_resource *resource) +{ + struct weston_seat *seat = get_seat(test); + struct weston_pointer *pointer = seat->pointer; + + wl_test_send_pointer_position(resource, pointer->x, pointer->y); +} + +static void +test_surface_configure(struct weston_surface *surface, int32_t sx, int32_t sy, int32_t width, int32_t height) +{ + struct weston_test_surface *test_surface = surface->configure_private; + struct weston_test *test = test_surface->test; + + if (wl_list_empty(&surface->layer_link)) + wl_list_insert(&test->layer.surface_list, + &surface->layer_link); + + weston_surface_configure(surface, test_surface->x, test_surface->y, + width, height); + + if (!weston_surface_is_mapped(surface)) + weston_surface_update_transform(surface); +} + +static void +move_surface(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *surface_resource, + int32_t x, int32_t y) +{ + struct weston_surface *surface = + wl_resource_get_user_data(surface_resource); + struct weston_test_surface *test_surface; + + surface->configure = test_surface_configure; + if (surface->configure_private == NULL) + surface->configure_private = malloc(sizeof *test_surface); + test_surface = surface->configure_private; + if (test_surface == NULL) { + wl_resource_post_no_memory(resource); + return; + } + + test_surface->surface = surface; + test_surface->test = wl_resource_get_user_data(resource); + test_surface->x = x; + test_surface->y = y; +} + +static void +move_pointer(struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_seat *seat = get_seat(test); + struct weston_pointer *pointer = seat->pointer; + + test->compositor->focus = 1; + + notify_motion(seat, 100, + wl_fixed_from_int(x) - pointer->x, + wl_fixed_from_int(y) - pointer->y); + + notify_pointer_position(test, resource); +} + +static void +send_button(struct wl_client *client, struct wl_resource *resource, + int32_t button, uint32_t state) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_seat *seat = get_seat(test); + + test->compositor->focus = 1; + + notify_button(seat, 100, button, state); +} + +static void +activate_surface(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *surface_resource) +{ + struct weston_surface *surface = surface_resource ? + wl_resource_get_user_data(surface_resource) : NULL; + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_seat *seat; + + seat = get_seat(test); + + if (surface) { + weston_surface_activate(surface, seat); + notify_keyboard_focus_in(seat, &seat->keyboard->keys, + STATE_UPDATE_AUTOMATIC); + } + else { + notify_keyboard_focus_out(seat); + weston_surface_activate(surface, seat); + } +} + +static void +send_key(struct wl_client *client, struct wl_resource *resource, + uint32_t key, enum wl_keyboard_key_state state) +{ + struct weston_test *test = wl_resource_get_user_data(resource); + struct weston_seat *seat = get_seat(test); + + test->compositor->focus = 1; + + notify_key(seat, 100, key, state, STATE_UPDATE_AUTOMATIC); +} + +static const struct wl_test_interface test_implementation = { + move_surface, + move_pointer, + send_button, + activate_surface, + send_key +}; + +static void +bind_test(struct wl_client *client, void *data, uint32_t version, uint32_t id) +{ + struct weston_test *test = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &wl_test_interface, 1, id); + wl_resource_set_implementation(resource, + &test_implementation, test, NULL); + + notify_pointer_position(test, resource); +} + +static void +idle_launch_client(void *data) +{ + struct weston_test *test = data; + pid_t pid; + sigset_t allsigs; + char *path; + + path = getenv("WESTON_TEST_CLIENT_PATH"); + if (path == NULL) + exit(EXIT_FAILURE); + pid = fork(); + if (pid == -1) + exit(EXIT_FAILURE); + if (pid == 0) { + sigfillset(&allsigs); + sigprocmask(SIG_UNBLOCK, &allsigs, NULL); + execl(path, path, NULL); + weston_log("compositor: executing '%s' failed: %m\n", path); + exit(EXIT_FAILURE); + } + + test->process.pid = pid; + test->process.cleanup = test_client_sigchld; + weston_watch_process(&test->process); +} + +WL_EXPORT int +module_init(struct weston_compositor *ec, + int *argc, char *argv[]) +{ + struct weston_test *test; + struct wl_event_loop *loop; + + test = zalloc(sizeof *test); + if (test == NULL) + return -1; + + test->compositor = ec; + weston_layer_init(&test->layer, &ec->cursor_layer.link); + + if (wl_global_create(ec->wl_display, &wl_test_interface, 1, + test, bind_test) == NULL) + return -1; + + loop = wl_display_get_event_loop(ec->wl_display); + wl_event_loop_add_idle(loop, idle_launch_client, test); + + return 0; +} diff --git a/tests/weston-tests-env b/tests/weston-tests-env new file mode 100755 index 00000000..b7322507 --- /dev/null +++ b/tests/weston-tests-env @@ -0,0 +1,43 @@ +#!/bin/bash + +TESTNAME=$1 + +if test -z "$TESTNAME"; then + echo "usage: $(basename $0) " + exit 1; +fi + +WESTON=$abs_builddir/../src/weston +LOGDIR=$abs_builddir/logs + +mkdir -p "$LOGDIR" + +SERVERLOG="$LOGDIR/$1-serverlog.txt" +OUTLOG="$LOGDIR/$1-log.txt" + +rm -f "$SERVERLOG" + +if test x$WAYLAND_DISPLAY != x; then + BACKEND=$abs_builddir/../src/.libs/wayland-backend.so +elif test x$DISPLAY != x; then + BACKEND=$abs_builddir/../src/.libs/x11-backend.so +else + BACKEND=$abs_builddir/../src/.libs/wayland-backend.so +fi + +case $TESTNAME in + *.la|*.so) + $WESTON --backend=$BACKEND \ + --socket=test-$(basename $TESTNAME) \ + --modules=$abs_builddir/.libs/${TESTNAME/.la/.so},xwayland.so \ + --log="$SERVERLOG" \ + &> "$OUTLOG" + ;; + *) + WESTON_TEST_CLIENT_PATH=$abs_builddir/$TESTNAME $WESTON \ + --socket=test-$(basename $TESTNAME) \ + --backend=$BACKEND \ + --log="$SERVERLOG" \ + --modules=$abs_builddir/.libs/weston-test.so,xwayland.so \ + &> "$OUTLOG" +esac diff --git a/tests/xwayland-test.c b/tests/xwayland-test.c new file mode 100644 index 00000000..658453f3 --- /dev/null +++ b/tests/xwayland-test.c @@ -0,0 +1,142 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Author: Tiago Vignatti + * + * xwayland-test: the idea is to guarantee that XWayland infrastructure in + * general works with Weston. + */ + +#include "weston-test-runner.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static int +dri2_open(xcb_connection_t *c, xcb_screen_t *screen) +{ + xcb_dri2_connect_cookie_t cookie; + xcb_dri2_connect_reply_t *reply; + xcb_dri2_authenticate_cookie_t cookie_auth; + xcb_dri2_authenticate_reply_t *reply_auth; + char *driver, *device; + int fd; + drm_magic_t magic; + + cookie = xcb_dri2_connect(c, screen->root, XCB_DRI2_DRIVER_TYPE_DRI); + reply = xcb_dri2_connect_reply(c, cookie, 0); + assert(reply); + + driver = strndup(xcb_dri2_connect_driver_name (reply), + xcb_dri2_connect_driver_name_length (reply)); + device = strndup(xcb_dri2_connect_device_name (reply), + xcb_dri2_connect_device_name_length (reply)); + + fd = open(device, O_RDWR); + printf ("Trying connect to %s driver on %s\n", driver, device); + free(driver); + free(device); + + if (fd < 0) + return -1; + + drmGetMagic(fd, &magic); + + cookie_auth = xcb_dri2_authenticate(c, screen->root, magic); + reply_auth = xcb_dri2_authenticate_reply(c, cookie_auth, 0); + assert(reply_auth); + + return fd; +} + +static int +create_window(void) +{ + xcb_connection_t *c; + xcb_screen_t *screen; + xcb_window_t win; + int fd; + + c = xcb_connect (NULL, NULL); + if (c == NULL) { + printf("failed to get X11 connection\n"); + return -1; + } + + screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data; + + win = xcb_generate_id(c); + xcb_create_window(c, XCB_COPY_FROM_PARENT, win, screen->root, + 0, 0, 150, 150, 1, XCB_WINDOW_CLASS_INPUT_OUTPUT, + screen->root_visual, 0, NULL); + + xcb_change_property (c, XCB_PROP_MODE_REPLACE, win, + XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, + 5, "title"); + xcb_map_window(c, win); + xcb_flush(c); + + fd = dri2_open(c, screen); + if (fd < 0) + return -1; + + xcb_destroy_window(c, win); + xcb_disconnect(c); + return 0; +} + +/* + * Ideally, the X Window Manager (XWM) and Weston Wayland compositor shouldn't + * be in the same process because they are using two different protocol + * streams in which one does not interface with the other. Probably the + * biggest problem with such architecture are the potentials dead locks that + * it may occur. So hypothetically, an X client might issue an X11 blocking + * request via X (DRI2Authenticate) which in turn sends a Wayland blocking + * request for Weston process it. X is blocked. At the same time, XWM might be + * trying to process an XChangeProperty, so it requests a blocking X11 call to + * the X server (xcb_get_property_reply -> xcb_wait_for_reply) which therefore + * will blocks there. It's a deadlock situation and this test is trying to + * catch that. + */ +static void +check_dri2_authenticate(void) +{ + int i, num_tests; + + /* TODO: explain why num_tests times */ + num_tests = 10; + for (i = 0; i < num_tests; i++) + assert(create_window() == 0); +} + +TEST(xwayland_client_test) +{ + check_dri2_authenticate(); + exit(EXIT_SUCCESS); +} diff --git a/wayland-scanner.mk b/wayland-scanner.mk new file mode 100644 index 00000000..0a72062b --- /dev/null +++ b/wayland-scanner.mk @@ -0,0 +1,8 @@ +%-protocol.c : $(wayland_protocoldir)/%.xml + $(AM_V_GEN)$(wayland_scanner) code < $< > $@ + +%-server-protocol.h : $(wayland_protocoldir)/%.xml + $(AM_V_GEN)$(wayland_scanner) server-header < $< > $@ + +%-client-protocol.h : $(wayland_protocoldir)/%.xml + $(AM_V_GEN)$(wayland_scanner) client-header < $< > $@ diff --git a/wcap/.gitignore b/wcap/.gitignore new file mode 100644 index 00000000..67b722d1 --- /dev/null +++ b/wcap/.gitignore @@ -0,0 +1,3 @@ +wcap-decode +wcap-snapshot + diff --git a/wcap/Makefile.am b/wcap/Makefile.am new file mode 100644 index 00000000..338208e3 --- /dev/null +++ b/wcap/Makefile.am @@ -0,0 +1,9 @@ +bin_PROGRAMS = wcap-decode + +wcap_decode_SOURCES = \ + main.c \ + wcap-decode.c \ + wcap-decode.h + +wcap_decode_CFLAGS = $(GCC_CFLAGS) $(WCAP_CFLAGS) +wcap_decode_LDADD = $(WCAP_LIBS) diff --git a/wcap/README b/wcap/README new file mode 100644 index 00000000..0994a1bb --- /dev/null +++ b/wcap/README @@ -0,0 +1,99 @@ +WCAP Tools + +WCAP is the video capture format used by Weston (Weston CAPture). +It's a simple, lossless format, that encodes the difference between +frames as run-length encoded rectangles. It's a variable framerate +format, that only records new frames along with a timestamp when +something actually changes. + +Recording in Weston is started by pressing MOD+R and stopped by +pressing MOD+R again. Currently this leaves a capture.wcap file in +the cwd of the weston process. The file format is documented below +and Weston comes with the wcap-decode tool to convert the wcap file +into something more usable: + + - Extract single or all frames as individual png files. This will + produce a lossless screenshot, which is useful if you're trying to + screenshot a brief glitch or something like that that's hard to + capture with the screenshot tool. + + wcap-decode takes a number of options and a wcap file as its + arguments. Without anything else, it will show the screen size and + number of frames in the file. Pass --frame= to extract a + single frame or pass --all to extract all frames as png files: + + [krh@minato weston]$ wcap-snapshot capture.wcap + wcap file: size 1024x640, 176 frames + [krh@minato weston]$ wcap-snapshot capture.wcap 20 + wrote wcap-frame-20.png + wcap file: size 1024x640, 176 frames + + - Decode and the wcap file and dump it as a YUV4MPEG2 stream on + stdout. This format is compatible with most video encoders and can + be piped directly into a command line encoder such as vpxenc (part + of libvpx, encodes to a webm file) or theora_encode (part of + libtheora, encodes to a ogg theora file). + + Using vpxenc to encode a webm file would look something like this: + + [krh@minato weston]$ wcap-decode --yuv4mpeg2 ../capture.wcap | + vpxenc --target-bitrate=1024 --best -t 4 -o foo.webm - + + where we select target bitrate, pass -t 4 to let vpxenc use + multiple threads. To encode to Ogg Theora a command line like this + works: + + [krh@minato weston]$ wcap-decode ../capture.wcap --yuv4mpeg2 | + theora_encode - -o cap.ogv + + +WCAP File format + +The file format has a small header and then just consists of the +indivial frames. The header is + + uint32_t magic + uint32_t format + uint32_t width + uint32_t height + +all CPU endian 32 bit words. The magic number is + + #define WCAP_HEADER_MAGIC 0x57434150 + +and makes it easy to recognize a wcap file and verify that it's the +right endian. There are four supported pixel formats: + + #define WCAP_FORMAT_XRGB8888 0x34325258 + #define WCAP_FORMAT_XBGR8888 0x34324258 + #define WCAP_FORMAT_RGBX8888 0x34325852 + #define WCAP_FORMAT_BGRX8888 0x34325842 + +Each frame has a header: + + uint32_t msecs + uint32_t nrects + +which specifies a timestamp in ms and the number of rectangles that +changed since previous frame. The timestamps are typically just a raw +system timestamp and the first frame doesn't start from 0ms. + +A frame consists of a list of rectangles, each of which represents the +component-wise difference between the previous frame and the current +using a run-length encoding. The initial frame is decoded against a +previous frame of all 0x00000000 pixels. Each rectangle starts out +with + + int32_t x1 + int32_t y1 + int32_t x2 + int32_t y2 + +followed by (x2 - x1) * (y2 - y1) pixels, run-length encoded. The +run-length encoding uses the 'X' channel in the pixel format to encode +the length of the run. That is for WCAP_FORMAT_XRGB8888, for example, +the length of the run is in the upper 8 bits. For X values 0-0xdf, +the length is X + 1, for X above or equal to 0xe0, the run length is 1 +<< (X - 0xe0 + 7). That is, a pixel value of 0xe3000100, means that +the next 1024 pixels differ by RGB(0x00, 0x01, 0x00) from the previous +pixels. diff --git a/wcap/main.c b/wcap/main.c new file mode 100644 index 00000000..1e4605ae --- /dev/null +++ b/wcap/main.c @@ -0,0 +1,304 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wcap-decode.h" + +static void +write_png(struct wcap_decoder *decoder, const char *filename) +{ + cairo_surface_t *surface; + + surface = cairo_image_surface_create_for_data((unsigned char *) decoder->frame, + CAIRO_FORMAT_ARGB32, + decoder->width, + decoder->height, + decoder->width * 4); + cairo_surface_write_to_png(surface, filename); + cairo_surface_destroy(surface); +} + +static inline int +rgb_to_yuv(uint32_t format, uint32_t p, int *u, int *v) +{ + int r, g, b, y; + + switch (format) { + case WCAP_FORMAT_XRGB8888: + r = (p >> 16) & 0xff; + g = (p >> 8) & 0xff; + b = (p >> 0) & 0xff; + break; + case WCAP_FORMAT_XBGR8888: + r = (p >> 0) & 0xff; + g = (p >> 8) & 0xff; + b = (p >> 16) & 0xff; + break; + default: + assert(0); + } + + y = (19595 * r + 38469 * g + 7472 * b) >> 16; + if (y > 255) + y = 255; + + *u += 46727 * (r - y); + *v += 36962 * (b - y); + + return y; +} + +static inline +int clamp_uv(int u) +{ + int clamp = (u >> 18) + 128; + + if (clamp < 0) + return 0; + else if (clamp > 255) + return 255; + else + return clamp; +} + +static void +convert_to_yv12(struct wcap_decoder *decoder, unsigned char *out) +{ + unsigned char *y1, *y2, *u, *v; + uint32_t *p1, *p2, *end; + int i, u_accum, v_accum, stride0, stride1; + uint32_t format = decoder->format; + + stride0 = decoder->width; + stride1 = decoder->width / 2; + for (i = 0; i < decoder->height; i += 2) { + y1 = out + stride0 * i; + y2 = y1 + stride0; + v = out + stride0 * decoder->height + stride1 * i / 2; + u = v + stride1 * decoder->height / 2; + p1 = decoder->frame + decoder->width * i; + p2 = p1 + decoder->width; + end = p1 + decoder->width; + + while (p1 < end) { + u_accum = 0; + v_accum = 0; + y1[0] = rgb_to_yuv(format, p1[0], &u_accum, &v_accum); + y1[1] = rgb_to_yuv(format, p1[1], &u_accum, &v_accum); + y2[0] = rgb_to_yuv(format, p2[0], &u_accum, &v_accum); + y2[1] = rgb_to_yuv(format, p2[1], &u_accum, &v_accum); + u[0] = clamp_uv(u_accum); + v[0] = clamp_uv(v_accum); + + y1 += 2; + p1 += 2; + y2 += 2; + p2 += 2; + u++; + v++; + } + } +} + +static void +convert_to_yuv444(struct wcap_decoder *decoder, unsigned char *out) +{ + + unsigned char *yp, *up, *vp; + uint32_t *rp, *end; + int u, v; + int i, stride, psize; + uint32_t format = decoder->format; + + stride = decoder->width; + psize = stride * decoder->height; + for (i = 0; i < decoder->height; i++) { + yp = out + stride * i; + up = yp + (psize * 2); + vp = yp + (psize * 1); + rp = decoder->frame + decoder->width * i; + end = rp + decoder->width; + while (rp < end) { + u = 0; + v = 0; + yp[0] = rgb_to_yuv(format, rp[0], &u, &v); + up[0] = clamp_uv(u/.3); + vp[0] = clamp_uv(v/.3); + up++; + vp++; + yp++; + rp++; + } + } +} + +static void +output_yuv_frame(struct wcap_decoder *decoder, int depth) +{ + static unsigned char *out; + int size; + + if (depth == 444) { + size = decoder->width * decoder->height * 3; + } else { + size = decoder->width * decoder->height * 3 / 2; + } + if (out == NULL) + out = malloc(size); + + if (depth == 444) { + convert_to_yuv444(decoder, out); + } else { + convert_to_yv12(decoder, out); + } + + printf("FRAME\n"); + fwrite(out, 1, size, stdout); +} + +static void +usage(int exit_code) +{ + fprintf(stderr, "usage: wcap-decode " + "[--help] [--yuv4mpeg2] [--frame=] [--all] \n" + "\t[--rate=] \n\n" + "\t--help\t\t\tthis help text\n" + "\t--yuv4mpeg2\t\tdump wcap file to stdout in yuv4mpeg2 format\n" + "\t--yuv4mpeg2-444\t\tdump wcap file to stdout in yuv4mpeg2 444 format\n" + "\t--frame=\t\twrite out the given frame number as png\n" + "\t--all\t\t\twrite all frames as pngs\n" + "\t--rate=\treplay frame rate for yuv4mpeg2,\n" + "\t\t\t\tspecified as an integer fraction\n\n"); + + exit(exit_code); +} + +int main(int argc, char *argv[]) +{ + struct wcap_decoder *decoder; + int i, j, output_frame = -1, yuv4mpeg2 = 0, all = 0, has_frame; + int num = 30, denom = 1; + char filename[200]; + char *mode; + uint32_t msecs, frame_time, *frame, frame_size; + + for (i = 1, j = 1; i < argc; i++) { + if (strcmp(argv[i], "--yuv4mpeg2-444") == 0) { + yuv4mpeg2 = 444; + } else if (strcmp(argv[i], "--yuv4mpeg2") == 0) { + yuv4mpeg2 = 420; + } else if (strcmp(argv[i], "--help") == 0) { + usage(EXIT_SUCCESS); + } else if (strcmp(argv[i], "--all") == 0) { + all = 1; + } else if (sscanf(argv[i], "--frame=%d", &output_frame) == 1) { + ; + } else if (sscanf(argv[i], "--rate=%d", &num) == 1) { + ; + } else if (sscanf(argv[i], "--rate=%d:%d", &num, &denom) == 2) { + ; + } else if (strcmp(argv[i], "--") == 0) { + break; + } else if (argv[i][0] == '-') { + fprintf(stderr, + "unknown option or invalid argument: %s\n", argv[i]); + usage(EXIT_FAILURE); + } else { + argv[j++] = argv[i]; + } + } + argc = j; + + if (argc != 2) + usage(EXIT_FAILURE); + if (denom == 0) { + fprintf(stderr, "invalid rate, denom can not be 0\n"); + exit(EXIT_FAILURE); + } + + decoder = wcap_decoder_create(argv[1]); + + if (yuv4mpeg2 && isatty(1)) { + fprintf(stderr, "Not dumping yuv4mpeg2 data to terminal. Pipe output to a file or a process.\n"); + fprintf(stderr, "For example, to encode to webm, use something like\n\n"); + fprintf(stderr, "\t$ wcap-decode --yuv4mpeg2 ../capture.wcap |\n" + "\t\tvpxenc --target-bitrate=1024 --best -t 4 -o foo.webm -\n\n"); + + exit(EXIT_FAILURE); + } + + if (yuv4mpeg2) { + if (yuv4mpeg2 == 444) { + mode = "C444"; + } else { + mode = "C420jpeg"; + } + printf("YUV4MPEG2 %s W%d H%d F%d:%d Ip A0:0\n", + mode, decoder->width, decoder->height, num, denom); + fflush(stdout); + } + + i = 0; + has_frame = wcap_decoder_get_frame(decoder); + msecs = decoder->msecs; + frame_time = 1000 * denom / num; + frame_size = decoder->width * decoder->height * 4; + frame = malloc(frame_size); + while (has_frame) { + if (decoder->msecs >= msecs) + memcpy(frame, decoder->frame, frame_size); + if (all || i == output_frame) { + snprintf(filename, sizeof filename, + "wcap-frame-%d.png", i); + write_png(decoder, filename); + fprintf(stderr, "wrote %s\n", filename); + } + if (yuv4mpeg2) + output_yuv_frame(decoder, yuv4mpeg2); + i++; + msecs += frame_time; + while (decoder->msecs < msecs && has_frame) + has_frame = wcap_decoder_get_frame(decoder); + } + + fprintf(stderr, "wcap file: size %dx%d, %d frames\n", + decoder->width, decoder->height, i); + + wcap_decoder_destroy(decoder); + + return EXIT_SUCCESS; +} diff --git a/wcap/wcap-decode.c b/wcap/wcap-decode.c new file mode 100644 index 00000000..87d93379 --- /dev/null +++ b/wcap/wcap-decode.c @@ -0,0 +1,152 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wcap-decode.h" + +static void +wcap_decoder_decode_rectangle(struct wcap_decoder *decoder, + struct wcap_rectangle *rect) +{ + uint32_t v, *p = decoder->p, *d; + int width = rect->x2 - rect->x1, height = rect->y2 - rect->y1; + int x, i, j, k, l, count = width * height; + unsigned char r, g, b, dr, dg, db; + + d = decoder->frame + (rect->y2 - 1) * decoder->width; + x = rect->x1; + i = 0; + while (i < count) { + v = *p++; + l = v >> 24; + if (l < 0xe0) { + j = l + 1; + } else { + j = 1 << (l - 0xe0 + 7); + } + + dr = (v >> 16); + dg = (v >> 8); + db = (v >> 0); + for (k = 0; k < j; k++) { + r = (d[x] >> 16) + dr; + g = (d[x] >> 8) + dg; + b = (d[x] >> 0) + db; + d[x] = 0xff000000 | (r << 16) | (g << 8) | b; + x++; + if (x == rect->x2) { + x = rect->x1; + d -= decoder->width; + } + } + i += j; + } + + if (i != count) + printf("rle encoding longer than expected (%d expected %d)\n", + i, count); + + decoder->p = p; +} + +int +wcap_decoder_get_frame(struct wcap_decoder *decoder) +{ + struct wcap_rectangle *rects; + struct wcap_frame_header *header; + uint32_t i; + + if (decoder->p == decoder->end) + return 0; + + header = decoder->p; + decoder->msecs = header->msecs; + decoder->count++; + + rects = (void *) (header + 1); + decoder->p = (uint32_t *) (rects + header->nrects); + for (i = 0; i < header->nrects; i++) + wcap_decoder_decode_rectangle(decoder, &rects[i]); + + return 1; +} + +struct wcap_decoder * +wcap_decoder_create(const char *filename) +{ + struct wcap_decoder *decoder; + struct wcap_header *header; + int frame_size; + struct stat buf; + + decoder = malloc(sizeof *decoder); + if (decoder == NULL) + return NULL; + + decoder->fd = open(filename, O_RDONLY); + if (decoder->fd == -1) { + free(decoder); + return NULL; + } + + fstat(decoder->fd, &buf); + decoder->size = buf.st_size; + decoder->map = mmap(NULL, decoder->size, + PROT_READ, MAP_PRIVATE, decoder->fd, 0); + + header = decoder->map; + decoder->format = header->format; + decoder->count = 0; + decoder->width = header->width; + decoder->height = header->height; + decoder->p = header + 1; + decoder->end = decoder->map + decoder->size; + + frame_size = header->width * header->height * 4; + decoder->frame = malloc(frame_size); + memset(decoder->frame, 0, frame_size); + + return decoder; +} + +void +wcap_decoder_destroy(struct wcap_decoder *decoder) +{ + munmap(decoder->map, decoder->size); + close(decoder->fd); + free(decoder->frame); + free(decoder); +} diff --git a/wcap/wcap-decode.h b/wcap/wcap-decode.h new file mode 100644 index 00000000..d6304154 --- /dev/null +++ b/wcap/wcap-decode.h @@ -0,0 +1,63 @@ +/* + * Copyright © 2012 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _WCAP_DECODE_ +#define _WCAP_DECODE_ + +#define WCAP_HEADER_MAGIC 0x57434150 + +#define WCAP_FORMAT_XRGB8888 0x34325258 +#define WCAP_FORMAT_XBGR8888 0x34324258 +#define WCAP_FORMAT_RGBX8888 0x34325852 +#define WCAP_FORMAT_BGRX8888 0x34325842 + +struct wcap_header { + uint32_t magic; + uint32_t format; + uint32_t width, height; +}; + +struct wcap_frame_header { + uint32_t msecs; + uint32_t nrects; +}; + +struct wcap_rectangle { + int32_t x1, y1, x2, y2; +}; + +struct wcap_decoder { + int fd; + size_t size; + void *map, *p, *end; + uint32_t *frame; + uint32_t format; + uint32_t msecs; + uint32_t count; + int width, height; +}; + +int wcap_decoder_get_frame(struct wcap_decoder *decoder); +struct wcap_decoder *wcap_decoder_create(const char *filename); +void wcap_decoder_destroy(struct wcap_decoder *decoder); + +#endif diff --git a/weston.ini b/weston.ini new file mode 100644 index 00000000..70069316 --- /dev/null +++ b/weston.ini @@ -0,0 +1,66 @@ +[core] +#modules=xwayland.so,cms-colord.so +#shell=desktop-shell.so + +[shell] +background-image=/usr/share/backgrounds/gnome/Aqua.jpg +background-color=0xff002244 +background-type=tile +panel-color=0x90ff0000 +locking=true +animation=zoom +startup-animation=fade +#binding-modifier=ctrl +#num-workspaces=6 +#cursor-theme=whiteglass +#cursor-size=24 + +#lockscreen-icon=/usr/share/icons/gnome/256x256/actions/lock.png +#lockscreen=/usr/share/backgrounds/gnome/Garden.jpg +#homescreen=/usr/share/backgrounds/gnome/Blinds.jpg +#animation=fade + +[launcher] +icon=/usr/share/icons/gnome/24x24/apps/utilities-terminal.png +path=/usr/bin/gnome-terminal + +[launcher] +icon=/usr/share/icons/gnome/24x24/apps/utilities-terminal.png +path=/usr/bin/weston-terminal + +[launcher] +icon=/usr/share/icons/hicolor/24x24/apps/google-chrome.png +path=/usr/bin/google-chrome + +[launcher] +icon=/usr/share/icons/gnome/24x24/apps/arts.png +path=./clients/weston-flower + +[screensaver] +# Uncomment path to disable screensaver +path=/usr/libexec/weston-screensaver +duration=600 + +[input-method] +path=/usr/libexec/weston-keyboard + +#[output] +#name=LVDS1 +#mode=1680x1050 +#transform=90 +#icc_profile=/usr/share/color/icc/colord/Bluish.icc + +#[output] +#name=VGA1 +#mode=173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync +#transform=flipped + +#[output] +#name=X1 +#mode=1024x768 +#transform=flipped-270 + +#[touchpad] +#constant_accel_factor = 50 +#min_accel_factor = 0.16 +#max_accel_factor = 1.0 -- cgit v1.2.1